miguelgrinberg / turbo-flask

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

Stream without a return #51

Closed aguileraGit closed 2 months ago

aguileraGit commented 2 months ago

Hello! In one of the quickstart examples, you use a Turbo Stream to return information about the POST. Is it possible to Stream information while processing data, and a return JSON data at the end? I've tried to combine my use case and your stream example to show what I'm trying to achieve.

The processUpload function is called via JS Fetch API via the main page.

@app.route('/processUpload', methods=['POST'])
def processUpload():

    if request.method == 'POST':

        #Send Notification (Bootstrap Alert) saying file has been received 
        if turbo.can_stream():
            turbo.stream(
                turbo.append(render_template('_alerts.html'), target='divAlert'),
            )

        #Continue to process uploaded file

        #Send Notification about a warning in the file
        if turbo.can_stream():
            turbo.stream(
                turbo.append(render_template('_alerts.html'), target='divAlert'),
            )

        #Finally return JSON to Fetch call
        returnDict = {'status':'success',
                            'setKey: 'setValue',
        }

        return returnDict

The returnDict is important here because it updates other parts of the page via JS if the file was processed correctly (or not).

I've gotten this to work with Turbo.push, but users are then getting alerts for files they didn't upload. I don't have users, so I can't use Turbo.push and Target users.

Any guidance is appreciated!

miguelgrinberg commented 2 months ago

The turbo-stream data is the response, once you send it the request is completed. In the code above you are not streaming anything, since you are creating turbo stream data without returning it to the client.

You may want to look at the turbo-push option, which sends data asynchronously over WebSocket.

aguileraGit commented 2 months ago

That makes sense. I was confused why I would see the _alert.html changes in the fetch response.

The code works with turbo.push. But, I don't actually have users so the alerts are incorrectly pushed to all users.

I'm just spit-balling here. Could a Session be used instead of a User.

@turbo.user_id
def get_user_id():
    return uuid.uuid4()

@app.route('/')
def index():
    #Generate unique ID
    get_user_id()
    return render_template('index.html')

@app.route('/processUpload', methods=['POST'])
def processUpload():

    if request.method == 'POST':

        #Send Notification (Bootstrap Alert) saying file has been received 
        turbo.push(turbo.append(render_template('_alerts.html'), 'divAlert'), to=user_id)

        #Continue to process uploaded file

        #Send Notification about a warning in the file
       turbo.push(turbo.append(render_template('_alerts.html'), 'divAlert'), to=user_id)

        #Finally return JSON to Fetch call
        returnDict = {'status':'success',
                            'setKey: 'setValue',
        }

        return returnDict
miguelgrinberg commented 2 months ago

You will need to create the concept of users so that you can identify your connections and push updates only to the clients you select. A user can be just a uuid, you do not need to have a database of users or anything like that.

aguileraGit commented 2 months ago

Hello Miguel. While I hope to add the concept of users in the future. For now, each time the page is refreshed, a "new user is created on the fly" and lasts until they refresh the page. I'm sure this concept won't work for all cases. It does allow me to push while processing data in a function and send a JSON respond the fetch command. I'll include some code in case anybody runs into a similar case. Thanks again for this great implementation and support!

app = Flask(__name__)
turbo = Turbo(app)
app.secret_key = 'BAD_SECRET_KEY'

class userClass:
    def __init__(self):
        self.id = None

    def createUUID(self):
        id = uuid.uuid4()
        self.id = id
        return id

user = userClass()

@turbo.user_id
def get_user_id():
    return user.id

@app.route('/')
def index():
    #Create a new ID when the page is loaded. 
    #Each time the page is refreshed, a new ID/User is created
    user.createUUID()
    get_user_id()
    return render_template('index.html')

@app.route('/processUpload', methods=['POST'])
def processUpload():

    if request.method == 'POST':

        #Send Notification (Bootstrap Alert) saying file has been received 
        turbo.push(turbo.append(render_template('_alerts.html'), 'divAlert'), to=user.id)

        #Continue to process uploaded file

        #Send Notification about a warning in the file
       turbo.push(turbo.append(render_template('_alerts.html'), 'divAlert'), to=user.id)

        #Finally return JSON to Fetch call
        returnDict = {'status':'success',
                            'setKey: 'setValue',
        }

        return returnDict