diff --git a/src/content/docs/guides/python/flask/database.mdx b/src/content/docs/guides/python/flask/database.mdx index c1f0e389..d8ad8a05 100644 --- a/src/content/docs/guides/python/flask/database.mdx +++ b/src/content/docs/guides/python/flask/database.mdx @@ -103,4 +103,27 @@ import { Steps } from '@astrojs/starlight/components'; We use the `execute` method to execute an SQL query and the `fetchall` method to fetch all the results. - \ No newline at end of file + + +## Using SQLAlchemy + +While using the `sqlite3` module directly is fine for small projects, it can be cumbersome for larger projects. SQLAlchemy is a popular ORM (Object-Relational Mapping) library that makes it easy to interact with databases. + +To use SQLAlchemy, you need to install the `flask_sqlalchemy` package: + +```bash +pip install flask_sqlalchemy +``` + +You can then use the `SQLAlchemy` class to create a new database connection: + +```python +from flask import Flask +from flask_sqlalchemy import SQLAlchemy + +app = Flask(__name__) + +app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///blog.db" + +db = SQLAlchemy(app) +``` \ No newline at end of file diff --git a/src/content/docs/guides/python/flask/form-validation.mdx b/src/content/docs/guides/python/flask/form-validation.mdx deleted file mode 100644 index 6fb3c869..00000000 --- a/src/content/docs/guides/python/flask/form-validation.mdx +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Proper Form Handling -description: A simple website built using flask -sidebar: - order: 11 - badge: - text: 🚧 - variant: caution ---- - -This page is under construction. Please check back later. diff --git a/src/content/docs/guides/python/flask/forms.mdx b/src/content/docs/guides/python/flask/forms.mdx index a48f543d..95928837 100644 --- a/src/content/docs/guides/python/flask/forms.mdx +++ b/src/content/docs/guides/python/flask/forms.mdx @@ -7,13 +7,38 @@ sidebar: So far all of this work replicates what can already be done with a simple HTML file. However, the real power of Flask comes from its ability to interact with data. -In this section, we will create a simple website that allows users to update data. We will use a simple list of dictionaries to store the data. This is not a scalable solution, but it is a good starting point. +In this section, we will create a simple website that allows users to update data. We will use a simple list of dictionaries to store the data and eventually this will be converted to use a [database](../database). + +## Sending data from the webpage to the server + +All of our routes use the `GET` method by default. This means that when a user visits a page, the browser sends a `GET` request to the server to retrieve the page. While we don't need to specify the method when defining a route, we can do so by passing it in as an argument to the route decorator. + +```python +@app.route("/", methods=["GET"]) +``` + +As great as `GET` requests are, they only "get" data from the server and are unable to send data to the server. To send data to the server, we need to use a `POST` request, similar to "posting" the data to the server. We can do this by creating a form in the HTML template and setting the `method` attribute to `POST` and having a route that accepts `POST` requests. + +:::note +There are different types of [HTTP requests](https://http.dev/methods) with each of them having a different purpose. +::: + +HTML forms are used to collect user input and send it to the server. The form element has an `action` attribute that specifies the URL where the form data should be sent, and a `method` attribute that specifies the HTTP method to use when sending the form data, as we are sending data to the server we will use the `POST` method. + +```html +
+ + + +
+``` + +In the above form, we have two input fields, one for the name and one for the age. When the user submits the form, the data will be sent to the `/update` route as a `POST` request. ```python -// app.py from flask import Flask, render_template, request -app = Flask(__name__) +... data = [ {"name": "Alice", "age": 25}, @@ -21,9 +46,7 @@ data = [ {"name": "Charlie", "age": 35}, ] -@app.route("/") -def index(): - return render_template("index.html", data=data) +... @app.route("/update", methods=["POST"]) def update(): @@ -31,59 +54,28 @@ def update(): age = request.form["age"] data.append({"name": name, "age": age}) return render_template("index.html", data=data) - -if __name__ == "__main__": - app.run(debug=True) -``` - -```html -// templates/index.html - - - - Updating data - - - -

Updating data

- - - - - - - {% for item in data %} - - - - - {% endfor %} -
NameAge
{{ item["name"] }}{{ item["age"] }}
- - - - - - ``` import { Steps } from '@astrojs/starlight/components'; -1. Create a list of dictionaries to store the data. +1. Import the `request` object from Flask. + + The `request` object contains the data that the client sends to the server. + In this case, we are using the `form` attribute of the `request` object to access the form data sent by the client. + +2. Create a list of dictionaries to store the data. These dictionaries will have two keys: `name` and `age`. While the data is hard-coded in this example, it could be loaded from a database or an API. -2. Create a route to display the data. +3. Create a route to display the data. The route will render an HTML template that displays the data in a table. The template uses a for loop to iterate over the list of dictionaries and display the data in rows. -3. Create a route to update the data. +4. Create a route to update the data. The route will receive the updated data from a form submission which happens from an HTTP POST Request. It will update the corresponding dictionary in the list and then render the HTML template to display the updated data. @@ -94,6 +86,205 @@ import { Steps } from '@astrojs/starlight/components'; -## Using WTF-Forms +:::tip +You can handle both `GET` and `POST` requests in the same route by checking the request method using `request.method`. + +```python +@app.route("/", methods=["GET", "POST"]) +def index(): + if request.method == "POST": + # Handle the POST request + else: + # Handle the GET request +``` +::: + +## Using Flask-WTF + +The above example works, but it is not very user-friendly. Users have to manually type in the data, and there is no validation to ensure that the data is correct. + +A better approach is to use [Flask-WTF](https://flask-wtf.readthedocs.io), which is a Flask extension that makes it easy to work with forms. Flask-WTF uses [WTForms](https://wtforms.readthedocs.io) to define forms in Python and render them in HTML. + +### Installing Flask-WTF + +To install Flask-WTF, run the following command: + +```bash +pip install Flask-WTF +``` + +### Creating a form + +There is a lot of boilerplate code involved in creating forms using Flask-WTF, but it is worth it for the added functionality. + + + +1. Firstly we will need to import the necessary classes from `flask_wtf`, `wtforms`, and `wtforms.validators`. + + ```python + from flask_wtf import FlaskForm + from wtforms import StringField, IntegerField + from wtforms.validators import DataRequired, NumberRange + ``` + +2. Next, we will create a form class that inherits from `FlaskForm`. + + This will contains the fields that we want to display in the form and the validation logic for each field. + + ```python + class MyForm(FlaskForm): + name = StringField('name', validators=[DataRequired()]) + age = IntegerField('age', [NumberRange(min=0, max=120)]) + ``` + +3. We can now use this form in our route to render the form in the HTML template. + + ```python + @app.route('/') + def submit(): + form = MyForm() + return render_template('index.html', form=form) + ``` + + This will provide our form to the template, which can be used to render the form fields. + +4. We can now update the HTML template to render the form fields. + + ```html +
+ {{ form.csrf_token }} +

+ {{ form.name.label }} + {{ form.name }} +

+ + {% if form.name.errors %} + + {% endif %} + +

+ {{ form.age.label }} + {{ form.age }} +

+ + {% if form.age.errors %} + + {% endif %} + + +
+ ``` + + The `{{ form.csrf_token }}` is a security feature that prevents [Cross-Site Request Forgery (CSRF)](https://owasp.org/www-community/attacks/csrf) attacks. + + The `{{ form.name.label }}` and `{{ form.name }}` will render the label and input field for the name respectively. + + The `{{ form.name.errors }}` will render any validation errors for the name field. + + The same applies for the age field. + +5. Finally, we can update the route to handle the form submission. + + We will validate the form data and if it is valid, we will extract the data from the form and add it to the list of data. + + ```python + @app.route('/update', methods=['POST']) + def update(): + form = MyForm() + if form.validate_on_submit(): + name = form.name.data + age = form.age.data + data.append({"name": name, "age": age}) + return render_template('index.html', form=form, data=data) + ``` + + :::note + The `validate_on_submit()` method checks if the form has been submitted and if it is valid. + ::: + +
+ +Combining all of the above steps, we get the following code: + +```python +// app.py +from flask_wtf import FlaskForm +from wtforms import StringField, IntegerField +from wtforms.validators import DataRequired, NumberRange + +class MyForm(FlaskForm): + name = StringField('name', validators=[DataRequired()]) + age = IntegerField('age', [NumberRange(min=0, max=120)]) + +data = [ + {"name": "Alice", "age": 25}, + {"name": "Bob", "age": 30}, + {"name": "Charlie", "age": 35}, +] + +@app.route('/') +def submit(): + form = MyForm() + return render_template('index.html', form=form) + +@app.route('/update', methods=['POST']) +def update(): + form = MyForm() + if form.validate_on_submit(): + name = form.name.data + age = form.age.data + data.append({"name": name, "age": age}) + return render_template('index.html', form=form, data=data) +``` + +:::tip +Make sure to give each form it's own unique name to avoid conflicts. +::: + +```html +// templates/index.html +... + +
+ {{ form.csrf_token }} +

+ {{ form.name.label }} + {{ form.name }} +

+ + {% if form.name.errors %} + + {% endif %} + +

+ {{ form.age.label }} + {{ form.age }} +

+ + {% if form.age.errors %} + + {% endif %} + + +
+ +... +``` -https://flask.palletsprojects.com/en/3.0.x/patterns/wtforms/ \ No newline at end of file +With all of the above code, we now have a simple website that allows users to update data. Using Flask-WTF the forms will always match up to the data that is expected, and the validation logic will ensure that the data is correct. diff --git a/src/content/docs/guides/python/flask/sqlalchemy.mdx b/src/content/docs/guides/python/flask/sqlalchemy.mdx deleted file mode 100644 index 3d65589c..00000000 --- a/src/content/docs/guides/python/flask/sqlalchemy.mdx +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Handling Databases with SQLAlchemy -description: A simple website built using flask -sidebar: - order: 12 - badge: - text: 🚧 - variant: caution ---- - -This page is under construction. Please check back later.