Search code examples
pythonpython-3.xdjangodjango-formshtmx

Retrieve the selected item of a dropdown to use it in a sentence that I will print in a textarea


I have 3 dropdowns that work fine. My goal is to click on the button and print the text of the third dropdown inside the textarea (def result and result.html), precisely inside a sentence in def test1. For example if I select Madrid, I will print "The selected trip is Madrid".

enter image description here

I don't want to pass the sentence directly (with "Madrid" inside it) into def result (which is the function of result.html, i.e. of the textarea). I don't want the sentence construction (with "Madrid" in it) to be built inside the def result function

I want the text of the third dropdown (e.g. "Madrid"), to become a placeholder that is passed first in the def test1 function. It is here that the sentence will be created which will subsequently be printed in the textarea (def result). I need this because in future in def test1 I will also call other sentences from other python files. So it's like the def test1 function acts as a sort of filter between the dropdowns and the textarea (def result).

I don't get any errors, but the sentence doesn't print. I think the problem is because def test1 fails to retrieve options from the third dropdown (e.g. "Madrid").

models.py

from django.db import models

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

    def __str__(self):
        return self.name


class City(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    name = models.CharField(max_length=40)

    def __str__(self):
        return self.name


class FullRecord(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    city_departure = models.ForeignKey(City, on_delete=models.CASCADE, related_name="city_departure")
    city_destination = models.ForeignKey(City, on_delete=models.CASCADE, related_name="city_destination")

    @property
    def departure_destination(self):
        return f"{self.city_departure}-{self.city_destination}"

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

views.py

**from django.shortcuts import render
from .models import FullRecord, Country


def trip_selector(request):
    if "Hx-Request" in request.headers:
        trips = FullRecord.objects.none()
        if request.headers["Hx-Trigger"] == "id_trip":
            country_id = request.GET.get("country")
            if country_id != "":
                trips = FullRecord.objects.filter(country_id=country_id)
            return render(request, "trips.html", {"trips": trips})
        
        elif request.headers["Hx-Trigger"] == "id_extract_single_city":
            selected_trip = request.GET.get("trips")
            extracted_cities = []
            if selected_trip != "":
                trip = FullRecord.objects.get(id=int(selected_trip))
                trip_with_combined_names = trip.departure_destination
                split_trip = trip_with_combined_names.split("-")
                extracted_cities = split_trip
            return render(request, "extract_single_city.html", {"options": extracted_cities})
        
    else:
        countries = Country.objects.all()
        return render(request, "form.html", {"countries": countries})**
        
#Create sentence
def test1(request):
    sentence = ["The selected trip is ", "Congratulations you have selected the trip ", "You have currently selected travel ", "The currently selected trip is "]
    return f"{random.choice(sentence)} {options}" #ERROR????

#Print in textarea
def result(request):
    test_print_in_result = test1(request)
    return render(request,"result.html", {"test_print_in_result": test_print_in_result})

urls.py

**from django.urls import path
from . import views
urlpatterns = [
    path("", views.trip_selector, name="trips")
]**

admin.py

from django.contrib import admin

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

class Prossima(admin.ModelAdmin):
      list_display = ['id', 'country','departure_destination']
admin.site.register(FullRecord, Prossima)

admin.site.register(Country)
admin.site.register(City)

base.html:

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>University</title>

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>

    <!-- HTMX -->
    <script src="https://unpkg.com/[email protected]/dist/htmx.min.js"></script>
</head>
<body>

    <div class="container mt-4">
        {% block  main-content %}
        {% endblock %}
    </div>

    <!-- evitare che le richieste hx-post incorrano in errori CSRF con le viste Django -->
    <script>
        document.body.addEventListener('htmx:configRequest', (event) => {
            event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}'; //insert csrf token when performing AJAX request
        })
    </script>

</body>
</html>

forms.html:

{% extends 'base.html' %} 

{% block main-content %}
    <div class="flex justify-between px-10 gap-5 mt-5">
        <div class="flex flex-col w-full">
            <!-- First Combobox -->
            <label for="id_country">Country</label>

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

        
        <div class="flex flex-col w-full">
            <!-- Second Combobox ??????? (non lo so)-->
            <label for="id_trip">Trip</label>
            <select name="trips" id="id_trip"
                    hx-get="{% url 'trips' %}"
                    hx-include="[name=country]"
                    hx-indicator=".htmx-indicator"
                    hx-trigger="change from:#id_country">
            </select>
        </div>

        <!-- Third Combobox -->
        <div class="flex flex-col w-full">
            <label for="id_extract_single_city">Extract single city</label>
            <select name="extract_single_city" id="id_extract_single_city"
                    hx-get="{% url 'trips' %}"
                    hx-include="[name=trips]"
                    hx-indicator=".htmx-indicator"
                    hx-trigger="change from:#id_trip">

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

    <!-- Textarea-->
    {% include "result.html" %}

    <!-- Button-->
    <button type="button" class="mybtn" name="btn" hx-get="{% url 'result' %}" hx-swap="outerHTML" hx-target="#id_result" hx-indicator=".htmx-indicator">Button 1</button>



{% endblock main-content %}

trips.html:

<option value="">Please select</option>
{% for trip in trips %}
    <option value="{{ trip.id }}">{{ trip.city_departure }}-{{ trip.city_destination }}</option>
{% endfor %}

extract_single_city.html

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

result.html:

<textarea name="result" id="id_result">{{ test_print_in_result }}</textarea>

Solution

  • Quite easy to implement actually.

    Update forms.html:

    The important part here is that you should define the textarea field rather than returning it as a seperate template (it's unnecessary). Also, include the value of the extract_single_city select field by referencing its id in hx-include attribute. I don't know if you want to be able to insert multiple messages into the textarea, I assumed you do that's why I changed the htmx swap technique to beforeend so the new result is added below the existing ones. I added readonly atribute to the textarea tag so it's not editable.

    .....
    <div class="flex flex-col w-1/2 justify-end">
        <button class="bg-green-300  p-2 rounded"
                type="button"
                hx-get="{% url 'result' %}"
                hx-include="#id_extract_single_city"
                hx-target="#id_result"
                hx-swap="beforeend">Submit
        </button>
    </div>
    <div class="py-2 px-10 h-96 mt-5">
        <textarea name="result" id="id_result" readonly class="w-full h-full rounded px-2 py-3"></textarea>
    </div>
    ....
    

    Update the views:

    Please notice the create_sentence function is expecting a 'city' argument so it can create an appropriate sentence. You access the city value (supplied the htmx request) in the result function which returns an HttpResponse rather than a render function. This is because you don't need a template to return simple text.

    from django.http import HttpResponse
    
    # Create sentence
    def create_sentence(request):
        sentence = random.choice(
            [
                "The selected trip is",
                "Congrats, you have selected the trip",
                "You have currently selected to travel",
                "The currently selected trip is",
            ]
        )
        city = request.GET.get("extract_single_city")
        return f"{sentence} {city} \n"
    
    #Print in textarea
    def result(request):
        result = create_sentence(request)
        return HttpResponse(result)
        
    

    That should work.

    See example Working example