Django dependant filters using json list variable contained within a foreign key

Viewed 87

I have a form that a user can fill in. What I want is a dependant dropdown menu such that when a router (ce_hostname) is selected, the ports (l2_interfaces) for that router are selectable from the second dropdown (ce_wan_port). The list of ports are stored as a json list in the router and the router is stored as a foreign key.

models.py

#Unique order. Top of heirachy tree
class Order(models.Model):
    order_name = models.CharField(max_length=100, unique=True)#, null=True, blank=True)                         #Unique name of order

#For CE router definition. Required for all orders.
class Ce_Base(models.Model):
    ce_hostname = models.CharField(max_length=15, validators=[CE_HOSTNAME_REGEX], verbose_name="CE Hostname", help_text="Hostname of router.")
    l2_interfaces = JSONField(null=True)                                                                        #Layer 2 interfaces

#For defining WAN links
class Ce_Pe(models.Model):

    order_reference = models.ForeignKey(Order, null=True, on_delete=models.CASCADE)                             #Order reference
    ce_hostname = models.ForeignKey(Ce_Base, null=True, on_delete=models.CASCADE, verbose_name="CE Hostname", help_text="Hostname of CE router.")
    ce_wan_port = models.CharField(max_length=500, null=True, blank=True)

For the purposes of my dropdown, the ce_wan_port should be a dropdown menu of the l2_interfaces, which appears after a ce_hostname is selected.

Example l2_interfaces data

[
    "Gi0/0/0",
    "Gi0/0/1",
    "Gi0/0/2"
]

forms.py

class Ce_PeForm(ModelForm):
        class Meta:
                model = Ce_Pe
                fields = ['ce_hostname', 'ce_wan_port',]

        def __init__(self, *args, order_reference, **kwargs):
            super().__init__(*args, **kwargs)
            order_id = str(order_reference.id)
            self.fields['ce_hostname'].queryset = Ce_Base.objects.filter(order_reference=order_id,)

This is currently filtering for ce_hostname objects within the same order.

views.py

@login_required
def addCe_Pe(request, pk_test):
    order = Order.objects.get(id=pk_test)
    ce_pe_form = Ce_PeForm(order_reference=order)
    if request.method == 'POST':
        ce_pe_form = Ce_PeForm(request.POST, order_reference=order)
        ce_pe_form.instance.order_reference = order
        if ce_pe_form.is_valid():
            ce_pe_form.save()
            return redirect('/')

    context = {'ce_pe_form':ce_pe_form}
    return render(request, 'orchestration/ce_pe_form.html', context)

I tried sending the ce_hostname variable, but it does not make sense for me to do this as it can change each time the user changes the dropdown menu, not just on class init or post.

Here is my html.

{% extends "orchestration/base.html" %}
{% load static %}
{%load crispy_forms_tags %}
{% block content %}

<h1>CE PE WAN Link Definition</h1>
<div class="row">
        <div class="col-md-15">
                <div class="card card-body">

                        <form action="" method="POST">
                                {% csrf_token %}
                                {{ce_pe_form|crispy}}
                                <p></p>
                                <input type="submit" name="Submit">
                        </form>

                </div>
        </div>
</div>

Edit:

I am little further forward, but still can't figure out how to piece this together.

In my views.py I am able to get the objects I want and print them out. From this, I can see how I could get the correct list from the selected dropdown.

def addCe_Pe(request, pk_test):
    order = Order.objects.get(id=pk_test)
    ce_pe_form = Ce_PeForm(order_reference=order)
    if request.method == 'POST':
        ce_pe_form = Ce_PeForm(request.POST, order_reference=order)
        ce_hostname = request.POST.get('ce_hostname')
        ce_dropdown = Ce_Base.objects.get(id=ce_hostname)
        print(ce_dropdown.l2_interfaces)

In my forms.py, I am able to override the choices. I think this is what I want to do, but I am not sure how to do this dynamically as the user selects the ce_hostname from the dropdown.

class Ce_PeForm(ModelForm):
        class Meta:
                model = Ce_Pe
                fields = ['ce_hostname', 'pe_hostname', 'px_hostname', 'vrf', 'ce_wan_port', 'ce_local_device', 'ce_local_device_port', 'pe_wan_port', 'pe_local_device', 'pe_local_device_port', 'wan_vlan', 'outer_vlan', 'idn', 'vrf_22_co
st']

        def __init__(self, *args, order_reference, **kwargs):
            super().__init__(*args, **kwargs)
            order_id = str(order_reference.id)
            self.fields['ce_hostname'].queryset = Ce_Base.objects.filter(order_reference=order_id,)

            #self.fields['ce_wan_port'].filter(data)
            self.fields['ce_wan_port'].choices = [('Gi0/0','Gi0/0'), ('Gi0/1','Gi0/1')]
1 Answers

The form is only created once during rendering of the template. You would have to use JavaScript to manipulate the choice field. Either you render all possible routers and their interfaces in a script tag of your html that can be accessed by a js-file or you place an AJAX call based on your router choice to return the appropriate port choices and fill the field with the data from the response.

Related