English 中文(简体)
Django+HTMX: problem populating a dropdown, which is empty. Error Django code or Htmx? (Values is valid, i tried printing it in console)
原标题:
The bounty expires in 2 days. Answers to this question are eligible for a +50 reputation bounty. Horiatiki is looking for an answer from a reputable source.

I have three dropdowns and they are all dependent between them. The first and two dropdowns work fine, all ok, while the third dropdown doesn t work: it s empty with nothing inside.

The first dropdown menu, called trip_selector, correctly returns eg Spain, England, France. If I select Spain, then the second dropdown, called trip, is activated which correctly returns Madrid-Barcelona, Seville-Bilbao, etc. So, far so good, all ok, everything works fine.

THE PROBLEM: The problem is the third dropdown called extract_single_city in views.py, or in extract_single_city.html. In console i tried to print the items I would like to populate the dropdown (print(x), print(y) and print(options)), and they print for example Madrid and Barcelona values correctly, but I can t populate them in the dropdown. I don t get any errors. enter image description here

WHAT I WANT:

  • I would like to make the second trips dropdown dependent on the third extract_single_city. As you can see I use HTMX. I wish that when I select in the second dropdown (called trips) for example Madrid-Barcelona then the third dropdown should be populated in extract_single_city.htmlwith Madrid and Barcelona, one for each item: enter image description here So I would like to populate extract_single_city.html dropdown with option (i.e. x and y) of the function def extract_single_city. I would like to use split() and loop (for) for educational purposes

  • For the sake of order and cleanliness, i would like to use all dropdowns in one file (forms.html). Currently in forms.html there is only first dropdown, then second and third dropdown are external html pages like trips.html and extract_single_city.html. So I would like to delete trips.html and extract_single_city.html and use all dropdowns in forms.html

IMPORTANT NOTES (FOR INFORMATIONAL PURPOSES ONLY):

I would use a for loop in the html page, and then in views.py I would use split() and options = [x, y]:

  • I know I may not use the loop (for) in html page, but if possible: for educational purposes I would like to use the loop (for)
  • I know I may not be using split, so write options = [trip.city_departure, trip.city_destination] directly, but let me explain: just for info, for example if in the third dropdown I select Madrid-Barcelona, I will need x = Madrid, and y = Barcelona to call it in future in web app code. So I thought about creating two separate variables like x and y, and use list like options = [x, y] to scroll in the dropdown. Either way, either I use one way or I use another way it doesn t matter, because this doesn t imply that the code of my question doesn t currently work

What am I doing wrong? Am I doing something wrong in views.py, something in the html page or something in HTMX?

CODE:

extract_single_city.html

<select name="extract_single_city" id="id_extract_single_city" style="width:200px;" 
        hx-get="{% url  extract_single_city  %}" 
        hx-target="#id_extract_single_city" 
        hx-indicator=".htmx-indicator" 
        hx-trigger="change" 
        hx-swap="none">

    <option value="">Please select</option>

    {% for i in options %}
        <option value="{{ i }}">{{ i }}</option>
    {% endfor %}
    
</select>

views.py

from django.shortcuts import render
from .models import Full_Record
import random
from django.db.models import F

def trip_selector(request):
    countries = Full_Record.objects.values(pk=F( country__id ), name=F( country__name )).distinct()
    trips = []
    return render(request,  form.html , { countries : countries,  trips : trips})


def trips(request):
    if request.GET.get("country"):
        country = request.GET.get( country )
        trips = Full_Record.objects.filter(country=country)

    elif request.GET.get("trips"):
        selected_trip_id = int(request.GET.get( trips ))
        selected_trip = Full_Record.objects.get(id=selected_trip_id)
        request.session.update({"selected_trip_id": selected_trip_id})
        trips = Full_Record.objects.filter(country=selected_trip.country)

    return render(request,  trips.html , {"trips": trips})
    
#THIRD DROPDOWN: ERROR???
def extract_single_city(request):
    if request.GET.get("trips"):
        trip = Full_Record.objects.get(id=request.session.get("selected_trip_id"))
        partita = f"{trip.city_departure}-{trip.city_destination}"

        x,y = trip.split( - )
        options = [x, y]

        context =  {"options": options}

        print("This is test of option", options) 
        print("This is test of single x", x) 
        print("This is test of single y", y) 

    return render(request,  extract_single_city.html , context)

And more:

forms.html

{% extends  base.html  %} 

{% block content %}

<!-- First Combobox -->
<label for="id_country">Country</label>

<select name="country" id="id_country" 
        hx-get="{% url  trips  %}" 
        hx-target="#id_trip" 
        hx-indicator=".htmx-indicator" 
        hx-trigger="change"
        hx-swap="outerHTML">

  <option value="">Please select</option>
  {% for country in countries %}
  <option value="{{ country.pk }}">{{ country.name }}</option>
  {% endfor %}
</select>

<!-- Second Combobox ??????? (non lo so)-->
<label for="id_trip">Trip</label>
{% include "trips.html" %}


<!-- Third Combobox -->
<label for="id_extract_single_city">Soggetto</label>
<select name="extract_single_city" id="id_extract_single_city" style="width:200px;">
    <option value="">Please select</option>
</select>

{% endblock %}

trips.html

<!-- Second Combobox -->
<select name="trips" id="id_trip" style="width:200px;"  
        hx-get="{% url  extract_single_city  %}?trip_id={{ trip.id }}" 
        hx-target="#id_extract_single_city" 
        hx-indicator=".htmx-indicator" 
        hx-trigger="change" 
        hx-swap="outerHTML">
    <option value="">Please select</option>
    {% for trip in trips %}
        <option value="{{ trip.id }}">{{ trip.city_departure }}-{{ trip.city_destination }}</option>
    {% endfor %}
</select>

urls.py

from django.urls import path
from . import views

urlpatterns = [
    path(  , views.trip_selector, name="trip_selector"),
    #path(  , views.trips, name= trips )
    path( trips , views.trips, name= trips ),
    path( result , views.result, name= result ),
    path( extract_single_city , views.extract_single_city, name= extract_single_city ),

]

Thanks you all!!!

UPDATE

I insert the code of models.py, of admin.py, and two screenshots below of admin panel

models.py

from django.db import models

from django.db import models
from smart_selects.db_fields import ChainedForeignKey
from  django.contrib import admin

############ BASIC DATA ############

#Country
class Country(models.Model):
    name = models.CharField(max_length=40)

    def __str__(self):
        return self.name


#City
class City(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)

    name = models.CharField(max_length=40)
    def __str__(self):
        return self.name

############ FULL RECORD ############

class Full_Record(models.Model):
   
    country = models.ForeignKey(Country, on_delete=models.CASCADE)

    city_departure = ChainedForeignKey(
        City,
        related_name= city_departure_name ,
        chained_field="country",
        chained_model_field="country",
        show_all=False,
        auto_choose=True,
        sort=True)
    
    city_destination = ChainedForeignKey(
        City,
        related_name= city_destination_name ,
        chained_field="country",
        chained_model_field="country",
        show_all=False,
        auto_choose=True,
        sort=True)


    def trip(self):
        return f"{self.city_departure}-{self.city_destination}"
    

    def __str__(self):
        return self.country.name

NOTE: As you can see, first I insert the basic data Country and City, then in Full_Record I insert the line creating the combinations of trips (for example Madrid-Barcelona) from two dropdowns. As you can see, I use the data from Full_Record, because I get the countries and trip combinations from here

enter image description here

enter image description here

admin.py

from django.contrib import admin

# Register your models here.
from .models import Country, City, Full_Record

class Prossima(admin.ModelAdmin):
      list_display = [ id ,  country ,  trip ]
admin.site.register(Full_Record, Prossima)


admin.site.register(Country)
admin.site.register(City)
问题回答

Your wish to use a for loop in the templates while using HTMX can not be [easily] done without using HTML partials. This is because HTMX expects HTML fragments from the views. If you want to get rid of your current fragments, then you have to construct the HTML code in the view, something I don t think is really clean.

NOTE: I can t understand the model setup of your application and I don t know Spanish, so I can t replicate the relationships you have. For that reason, I used my own models that I think mirrors your setup (or at least can be easily used in your setup).

There are two solutions I have come up with:

  1. Not using a form, uses HTML partials (which I understand is what you re trying to avoid.
  2. Using Django forms, which I would argue is much cleaner.

models.py

from django.db import models
    
class Country(models.Model):
    name = models.CharField(max_length=50)


class Trip(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    name = models.CharField(max_length=50) # e.g Madrid-Bacerlona


class Resort(models.Model):
    # Ideally, this should have a foreignkey to Trip
    name = models.CharField(max_length=50) # e.g Madrid

Sample model data Trips example Resorts example

urls.py from django.urls import path from travel import views as travel_views

app_name = "travel"

urlpatterns = [
    path("", travel_views.TripBookingView.as_view(), name="book-trip"),
    path("trip/", travel_views.TripView.as_view(), name="trips"),
    path("resort/", travel_views.ResortView.as_view(), name="resorts")
]

FIRST SOLUTION (it is what you are already doing, but all dependents work

Have the main template with the markup you need: Country, Trip, Resort. In a view that serves this template, supply the countries variable with all objects (since Country is not dependent on any model).

views.py from django.shortcuts import render from travel import models as travel_models from django.views.generic import View

class TripBookingView(View):
    def get(self, request, *args, **kwargs):
        countries = travel_models.Country.objects.all()
        return render(request, "travel/book-trip.html", locals())

**book-trip.html**

Loop through the countries to to populate Country options. Leave the options for Trip and Resort empty. Country --- {% for country in countries %} {{ country.name }} {% endfor %} Trip <select name="trip" id="trip" class="w-full text-center" hx-get="{% url travel:trips %}" hx-trigger="change from:#country" // trigger by change event on #country hx-include="[name= country ]"> // include the country value in the request
Resort <select name="resort" id="resort" class="w-full text-center" hx-get="{% url travel:resorts %}" hx-trigger="change from:#trip" //trigger by change event on #trip hx-include="[name= trip ]"> // include the trip value in the value

views.py

class TripView(View):
    def get(self, request, *args, **kwargs):
        country_id = request.GET.get("country", "")
        # If no country was selected, don t return Trips
        trips = travel_models.Trip.objects.none() 
        if country_id != "":
            country_id = int(country_id)
            trips = travel_models.Trip.objects.filter(country__id=country_id)
        return render(request, "travel/partials/trips.html", {"trips": trips})


class ResortView(View):
    def get(self, request, *args, **kwargs):
        trips = request.GET.get("trip", None)
        # If not trip was selected, don t return Resorts
        resorts = travel_models.Resort.objects.none() 
        if trips is not None:
            trips = trips.split("-")
            resorts = travel_models.Resort.objects.filter(name__in=trips)
        return render(request, "travel/partials/resorts.html", 
{"resorts": resorts})

trips.html

<option value="">---</option>
{% for trip in trips %}
<option value="{{ trip.name }}">{{ trip.name }}</option>
{% endfor %}

resorts.html

<option value="">---</option>
{% for resort in resorts %}
    <option value="{{ resort.id }}">{{ resort.name }}</option>
{% endfor %}

This works fine. But I think it is unncessary and dirty. See the following solution instead.

SECOND SOLUTION

The approach is to use Django s modelforms. First, create a BookTrip model with foreign keys to Country, Trip, Resort.

models.py

........
class BookTrip(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    trip = models.ForeignKey(Trip, on_delete=models.CASCADE)
    resort = models.ForeignKey(Resort, on_delete=models.CASCADE)

forms.py

from django import forms
from travel import models as travel_models
from django.urls import reverse_lazy

class BookTravelForm(forms.ModelForm):
    class Meta:
        model = travel_models.BookTrip
        fields = "__all__"
        # Add HTMX attributes to  trip  and  resort  fields
        widgets = {
            "trip": forms.Select(attrs={
                "hx-get": reverse_lazy("travel:book-trip"),
                # Only make the request if the  country  field was changed
                "hx-trigger": "change from:#id_country",
                # Include the form values in the request
                "hx-include":  form ,
                "hx-target": "body",
            }),
            "resort": forms.Select(attrs={
                "hx-get": reverse_lazy("travel:book-trip"),
                # Only make the request if the  trip  field was changed
                "hx-trigger": "change from:#id_trip",
                # Include the form values in the request
                "hx-include":  form ,
                "hx-target": "body",
            })
        }

views.py

from django.shortcuts import render
from travel import models as travel_models
from django.views.generic import View
from travel import forms as travel_forms

def book_visit_view(request):
    form = travel_forms.BookTravelForm()
    if "Hx-Request" in request.headers: # Check if request is coming from HTMX
        # Bind the form with request.GET parameters
        form = travel_forms.BookTravelForm(request.GET)
        # If request was triggered by the  trip  field i.e. Country field was changed
        #
        if request.headers["Hx-Trigger"] == "id_trip":
            country_id = request.GET.get("country")
            if country_id != "":
                # Update  trip  and  resort  fields  queryset
                form.fields["trip"].queryset = travel_models.Trip.objects.filter(country_id=country_id)
                form.fields["resort"].queryset = travel_models.Resort.objects.none()
        elif request.headers["Hx-Trigger"] == "id_resort":
            trip_id = request.GET.get("trip")
            if trip_id != "":
                # Get the name field on Trip model instance
                trip_name = travel_models.Trip.objects.get(id=int(trip_id)).name
                # Because the name is seperated by a  - , you can use the split method
                names_split = trip_name.split("-")
                form.fields["resort"].queryset = travel_models.Resort.objects.filter(name__in=names_split)
        return render(request, "travel/book-trip.html", locals())

    else:
        # form = travel_forms.BookTravelForm()
        countries = travel_models.Country.objects.all()
        form.fields["trip"].queryset = travel_models.Trip.objects.none()
        form.fields["resort"].queryset = travel_models.Resort.objects.none()
        return render(request, "travel/book-trip.html", locals())

urls.py

from django.urls import path
from travel import views as travel_views
app_name = "travel"

urlpatterns = [
    path("", travel_views.book_visit_view, name="book-trip"),
]

and finally, book-trip.html

<form method="post">
<div class="flex justify-evenly gap-5 text-center mt-10 p-2 shadow-lg shadow-black rounded-lg border border-black">
    {% for field in form %}
        <div class="flex flex-col gap-2 bg-cyan-400 w-full">
            {{ field.label_tag }}
            {{ field }}
        </div>
    {% endfor %}
</div>
</form>

I prefer this solution because you don t need extra view functions or any HTML partials, and the form-rendering template is much much cleaner. Results are the same.

Working examples Selecting trip
Selecting resort





相关问题
Can Django models use MySQL functions?

Is there a way to force Django models to pass a field to a MySQL function every time the model data is read or loaded? To clarify what I mean in SQL, I want the Django model to produce something like ...

An enterprise scheduler for python (like quartz)

I am looking for an enterprise tasks scheduler for python, like quartz is for Java. Requirements: Persistent: if the process restarts or the machine restarts, then all the jobs must stay there and ...

How to remove unique, then duplicate dictionaries in a list?

Given the following list that contains some duplicate and some unique dictionaries, what is the best method to remove unique dictionaries first, then reduce the duplicate dictionaries to single ...

What is suggested seed value to use with random.seed()?

Simple enough question: I m using python random module to generate random integers. I want to know what is the suggested value to use with the random.seed() function? Currently I am letting this ...

How can I make the PyDev editor selectively ignore errors?

I m using PyDev under Eclipse to write some Jython code. I ve got numerous instances where I need to do something like this: import com.work.project.component.client.Interface.ISubInterface as ...

How do I profile `paster serve` s startup time?

Python s paster serve app.ini is taking longer than I would like to be ready for the first request. I know how to profile requests with middleware, but how do I profile the initialization time? I ...

Pragmatically adding give-aways/freebies to an online store

Our business currently has an online store and recently we ve been offering free specials to our customers. Right now, we simply display the special and give the buyer a notice stating we will add the ...

Converting Dictionary to List? [duplicate]

I m trying to convert a Python dictionary into a Python list, in order to perform some calculations. #My dictionary dict = {} dict[ Capital ]="London" dict[ Food ]="Fish&Chips" dict[ 2012 ]="...

热门标签