Search code examples
pythonhtmlrequestputbottle

How to handle a put request from the html form using bottle framework


I am using bottle, and I want to handle a put request from HTML and update a nested dictionary. I know I can do front-end validation but I am currently working on an assignment where this is a requirement.

here is my dictionary of items:

items = {
    'daf-394kd-d-39823-393': {
        "name": "a", "lastname": "a"
    },
    'daf-djfk3-32983-393dg': {
        "name": "b", "lastname": "b"
    }
}

and here is my route:

@put("/items/<item_id>")
def _(item_id):

    new_name = request.forms.get("name")
    new_lastname = request.forms.get("lastname")

    items[item_id] = {"name": new_name, "lastname": new_lastname}

    return redirect("/items")

and here is my HTML code:

<form action="/items" method="post">
   <input type="hidden" name="_method" value="put">
   <label for="">NAME</label>
   <input name="name" type="text" placeholder="name">
   <label for="">LAST NAME</label><br>
   <input name="lastname" type="text" placeholder="lastname"><br><br>
   <button>Update</button>
</form>

I tried to use a hidden input in order to change the post method to put but it doesn't seem to work.


Solution

  • Surprisingly, I don't see anything in the documentation, GitHub issues, Stack Overflow, or the internet in general about how to add a hidden field to your form to tell Bottle to call the PUT handler for a form submission. Workarounds are to use JS/AJAX to send a PUT request or to use a POST with PUT (update) semantics:

    server.py

    from bottle import redirect, request, route, run, template
    
    items = {
        "daf-394kd-d-39823-393": {
            "name": "a",
            "lastname": "a"
        },
        "daf-djfk3-32983-393dg": {
            "name": "b",
            "lastname": "b"
        }
    }
    
    @route("/items/<item_id>", method="POST")
    def items_update(item_id):
        new_name = request.forms.get("name")
        new_lastname = request.forms.get("lastname")
        items[item_id] = {"name": new_name, "lastname": new_lastname}
        return redirect("/items")
    
    @route("/items")
    def items_all():
        return template("items.tpl", items=items)
    
    run(host="localhost", port=8080)
    

    items.tpl

    <!DOCTYPE html>
    <html>
    <body>
    % for k, v in items.items():
      <div>
        <div>
          id: {{k}}, first name: '{{v["name"]}}', last name: '{{v["lastname"]}}'
        </div>
        <form action="/items/{{k}}" method="post">
          <label for="name">NAME</label>
          <input name="name" placeholder="name">
          <label for="lastname">LAST NAME</label>
          <input name="lastname" placeholder="lastname">
          <input type="submit">
        </form>
      </div>
    % end
    </body>
    </html>
    

    This is less than satisfying since the same route would need explicit branches to differentiate actions:

    server.py (partial)

    @route("/items/<item_id>", method="POST")
    def items_update_delete(item_id):
        if request.forms.get("_method") == "put":
            new_name = request.forms.get("name")
            new_lastname = request.forms.get("lastname")
            items[item_id] = {"name": new_name, "lastname": new_lastname}
        elif request.forms.get("_method") == "delete":
            del items[item_id]
    
        return redirect("/items")
    

    index.tpl

    <!DOCTYPE html>
    <html>
    <body>
    % for k, v in items.items():
      <div>
        <div>
          id: {{k}}, first name: '{{v["name"]}}', last name: '{{v["lastname"]}}'
        </div>
        <form action="/items/{{k}}" method="post">
          <input type="hidden" name="_method" value="put" />
          <label for="name">NAME</label>
          <input name="name" placeholder="name">
          <label for="lastname">LAST NAME</label>
          <input name="lastname" placeholder="lastname">
          <input type="submit">
        </form>
        <form action="/items/{{k}}" method="post">
          <input type="hidden" name="_method" value="delete" />
          <input type="submit" value="Delete">
        </form>
      </div>
    % end
    </body>
    </html>
    

    I'd be happy to see a more idiomatic answer that shows the correct hidden field for triggering Bottle's PUT route and I'll update this answer if I come across it. Opening an issue in Bottle's GitHub repo might be worth doing.