C. Crt
C. Crt

Reputation: 355

Get data from a dropdown menu with FastAPI

In a FastAPI project, you can easily get data from a HTML form to the back-end. There are built-in ways to get data from a text input, a file upload, etc. However, dropdown menus don't seem to work in my project. FastAPI developer Tiangolo has addressed the issue after being requested and made a tutorial page including dropdown menus. I tried to follow the same steps as him but I cannot get data from a dropdown menu into my backend.

My code looks like this:

class dropdownChoices(str, Enum):
    water = "WATER"
    fire = "FIRE"
    electric = "ELECTRIC"
    grass = "GRASS"
    donut = "DONUT"

@router.get('/upload')
def upload(request: Request):
    return templates.TemplateResponse('upload.html', context={'request': request, 'choices': [e.value for e in dropdownChoices]})
<form action="/upload" method="post" enctype="multipart/form-data">
    <div class="form-group">
        <label for="choices_dropdown">Choose:</label>
        <select id="choices_dropdown" name="dropdown_choices">
            {% for choice in choices %}
            <option value={{choice}}>{{choice}}</option>
            <!-- The choices are correctly displayed in the dropdown menu -->
            {% endfor %}
        </select>
    </div>
    <!-- More form actions including file upload and checkbox. These work. -->
    <div class="form-group">
        <label for="upload_file">Choose Upload File:</label>
        <input type="file" class="form-control-file" name='upload_file' id="upload_file">
        <input class="form-check-input" type="checkbox" name='overwrite_existing' id="flexCheckChecked"> Overwrite existing
    </div>
    <button id="upload" type='submit' class="btn btn-primary">Upload</button>
</form>
# Gets data from upload.html
@app.post("/upload")
async def handle_form(request: Request,
                      choice: str = "WATER",
                      upload_file: UploadFile = File(...),
                      overwrite_existing: bool = Form(False)):
    print(choice) #does NOT work: always print default ("WATER")
    print(overwrite_existing) #Works, prints true or false depending on input
    contents = await upload_file.file.read() #Works, file is later read etc
    return templates.TemplateResponse('upload.html', context={'request': request,
                                                              'choices': [e.value for e in view.dropdownChoices]})

I feel like I have followed the tutorial thoroughly, yet I always get the default choice. If I don't put a default choice in my handle_form() method, I get nothing at all. I don't understand why the user's choice form the dropdown menu is not transmitted like the rest.

Upvotes: 3

Views: 11777

Answers (2)

LotB
LotB

Reputation: 491

You always get the default choice because you set that, and it isn't sent with a matching name.

choice: str = "WATER",

here, you say you expect choice, not as the enum but as a plain str, and you set the default value as literal str "WATER". But on your front end, you send it with name as dropdown_choices.

<select id="choices_dropdown" name="dropdown_choices">

Furthermore, with such a declaration, Fastapi would be expecting the value to be sent as a query parameter, but your front-end is sending it as part of the body as form data. For fastapi to properly take it where it is, and validate it correctly, you need to have both a matching name and type.

async def handle_form(request: Request,
                      dropdown_choices: dropdownChoices = Form(dropdownChoices.water),
                      upload_file: UploadFile = File(...),
                      overwrite_existing: bool = Form(False)):

The value is now declared with the proper name, and should be parsed and validated as the enum directly.The default value is also declared as a member of the enum, instead of an unrelated str.

Upvotes: 2

MatsLindh
MatsLindh

Reputation: 52892

Your name in the form is dropdown_choices. Your name in your FastAPI endpoint definition is choice. These need to be identical. You also want to tell FastAPI that this is a Form field as well (as you did with your checkbox):

choice: str = Form("WATER"), 

You should also wrap the option value in "":

<option value="{{choice}}">

There is nothing magic about select boxes; data gets submitted in the usual ways - either through GET or through POST.

Upvotes: 1

Related Questions