miguelgrinberg / turbo-flask

Integration of Hotwire's Turbo library with Flask.
MIT License
301 stars 35 forks source link

can_stream is False as well as can_push #46

Closed Skealz closed 1 year ago

Skealz commented 1 year ago

Hi,

I am trying to use Turbo-Flask to update a Form (based on which value the user select in several Select fields), without updating the rest of the HTML page.

I tried using Werkzeug and also gunicorn + Nginx (I added the nginx sample config I found in the docs to Upgrade the connection).

Here is some code samples :

<form id="basic_form" method="post" style="padding-top:2%;" action="/center/center_nav">
<input id="csrf_token" name="csrf_token" type="hidden" value="ImZmMmQ3MTNlZmQ0Y2ZlZTI4NTljNzdjMDMyZGY0NmY3Yjc4OGQ1OTgi.ZBCjcg.tQJVvBL_ALsVc6yA_LlPPkCSofk">
    <div class="row" style="position: relative;">
            <div class="col s1">
                  <label for="year">Year</label>
                  <select class="browser-default" id="year" name="year" onchange="this.form.submit()"><option selected="" value="2023">2023</option><option value="2022">2022</option><option value="2021">2021</option><option value="2020">2020</option><option value="2019">2019</option><option value="2018">2018</option><option value="2017">2017</option><option value="2016">2016</option><option value="2015">2015</option><option value="2014">2014</option><option value="2013">2013</option><option value="2012">2012</option><option value="2011">2011</option><option value="2010">2010</option></select>
            </div>
            <div class="col s1">
                  <label for="basin">Basin</label>
                  <select class="browser-default" id="basin" name="basin" onchange="this.form.submit()"><option value="All">All</option><option value="SI">SI</option><option value="SP">SP</option></select>
            </div>
            <div class="col s1">
                  <label for="cyclone">Storm Name</label>
                  <select class="browser-default" id="cyclone" name="cyclone" onchange="this.form.submit()"><option value="All">All</option><option value="CHENESO">CHENESO</option><option value="DINGANI">DINGANI</option><option value="FREDDY">FREDDY</option><option value="HALE">HALE</option><option value="GABRIELLE">GABRIELLE</option></select>
            </div>
        <button class="btn waves-effect waves-light blue-grey lighten-1" type="submit" style="position: absolute; bottom: 0;">Filter</button>
    </div>
</form>

The form is auto-submitted when the user select a value in Select fields (onchange="this.form.submit())

@bp.route('/center_nav', methods=["GET", "POST"])
@timing
def center_nav():
...some code...
    if request.method == 'POST':
      form = FilterCenterForm(request.form, bla, bla)
                  print("stream", turbo.can_stream())
                  if turbo.can_stream():
                      # basic_form correspond to the HTML form id defined in basic_form_filter.html
                      return turbo.stream(turbo.replace(render_template("basic_form_filter.html", form=form,
                                                                        form_fields_opts=form_fields_opts()),
                                                        'basic_form'))

turbo.can_stream or turbo.can_push are systematically False. It seems that the endpoint turbo-stream works (at least in gunicorn, sometimes in Werkzeug but it is another issue as I read on other issues)

It feels I'm doing something wrong but I don't know what.

Versions :

(/opt/anaconda/cyclobs_t) $ mamba list | grep flask
flask                     2.0.3              pyhd8ed1ab_0    conda-forge
flask-sock                0.6.0                      py_0    
flask-sqlalchemy          2.5.1              pyhd8ed1ab_0    conda-forge
flask-wtf                 1.0.1              pyhd8ed1ab_0    conda-forge
turbo-flask               0.8.4                      py_0    

(I builded conda packages for flask-sock and turbo-flask)

miguelgrinberg commented 1 year ago

None of the code that you show suggests a problem. The can_stream() method simply checks if the client indicated that it accepts turbo-stream responses. My guess is that this is False because your page has not loaded turbo.js. Is that possible?

Skealz commented 1 year ago

When I inspect HTML, I see, in head :

<script src="https://cdn.jsdelivr.net/npm/@hotwired/turbo@7.3.0/dist/turbo.es2017-umd.js"></script>
<script>Turbo.connectStreamSource(new WebSocket(`ws${location.protocol.substring(4)}//${location.host}/turbo-stream`));</script>

Maybe I can re-check in the browser console if turbo is available, with some commands ?

I wonder if it could be something linked to the way the form is submitted, as it is submitted using select fields.

Thanks for your answer !

miguelgrinberg commented 1 year ago

@Skealz I bet it is the way you submit the form, maybe it triggers a bug in turbo.js or they just don't intend to support JS submissions.

Skealz commented 1 year ago

Thanks for your comments, this is indeed the way I submit the form that caused the issue. I managed to fix the issue with this code :

<form id="basic_form" method=post style="padding-top:2%;" action="{{url_submit_form}}">
    {{ form.hidden_tag() }}
    <div class="row" style="position: relative;">
        {% for field in form if field.widget.input_type != 'hidden' %}
            <div class="col s1">
                  {{ field.label }}
                {% if form_fields_opts is defined %}
                  {{ field(class_="browser-default", **form_fields_opts[field.name])|safe }}
                {% else %}
                  {{ field(class_="browser-default")|safe }}
                {% endif %}
            </div>
        {% endfor %}
        <button class="btn waves-effect waves-light blue-grey lighten-1" type="submit"
                style="position: absolute; bottom: 0;">Filter</button>
    </div>
</form>

<script>
$(document).ready(function() {
    $('#basic_form select').change(function() {
        $('#basic_form button[type=submit]').click();
    });
});
</script>
janpeterka commented 1 year ago

There is nice Hotwire (Turbo+Stimulus) tutorial for exactly this here: https://twitter.com/ilrock__/status/1634244554462248972

It's in Rails, but can be easily used in Flask also.