The purpose of this project is to be used as a starting skeleton for a Flask project. Also, a list of exercises will let you understand how a simple web app works.
Create a new venv if you do not have one in the root of project python3 -m venv venv
and activate it using source venv/bin/activate
.
Another way of creating a virtualenv is to use the IDE and do it interactively.
Install all the packages from the requirements.txt
file:
pip install -r requirements.txt
And you are ready to run the skeleton project with python app.py
Now, take a quick look on the Quickstart page.
The following exercises will lead to a simple quotes manager application where you can create a quote, delete or view the dataset.
- Change the method
hello
(app.py) for the root endpoint to render the existing index.html file in thetemplates
directory. You'll have to use the render_template method presented in here. - Read about base layouts and migrate the existing
index.html
to a base templatebase.html
which will contain the HTML standard structure with two{% block something %}{% endblock %}
for title and content. Also, use the learning block style to migrate the index.html to the new format extended from yourbase.html
. - Create a new route
/contact
inapp.py
and return a dummy contact page template. As a bonus, you can add hyperlink in yourindex.html
for your/contact
route. - In the templates directory, you'll find a
add.html
file which contains an HTML form. Complete theadd
method found inapp.py
to get the submitted data from the form (author, quote) and print it in the console. - For the template files mentioned above
contact.html
,add.html
and the future ones, adapt them to the new structure, using the base template. - Make a new route
/view
where its method accepts two arguments author and quote. In this one, render theview.html
template passing the author and quote kwargs like in this example. - On the method for route
/add
, after you are loading the form data, return a redirect to the/view
route and pass the required kwargs example.
I added a mariadb docker service in the docker-compose.yml
file. You can start the service with docker-compose up -d
.
If you are not using docker-compose, and want to use the mariadb image directly with docker
, do the following steps:
- create a volume for the mariadb container
docker volume create --name mariadb_data
- create the mariadb container configured with its persistent volume and the exposed port
3306
docker run -d --name mariadb -p 3306:3306 -e ALLOW_EMPTY_PASSWORD=yes -e MARIADB_USER=user -e MARIADB_DATABASE=test -e MARIADB_PASSWORD=test --volume mariadb_data:/bitnami bitnami/mariadb:latest
You can see your new container by typing docker ps
.
- Populate the
get_connection
found atservices/db.py
to return a connection to your database using themysql.connector
- examples. Also, do not forget to use the right variables from__init__
. - Populate the
close
andcommit
methods found in theDBService
class. - In the
__main__
part ofservices/db.py
execute a SQL query that creates a new tablefamous_quotes
with author and quote as fields. Do not forget to use yourDBService
class. - Populate the
insert_quote
method fromDBService
class to connect to your database and insert a received quote with its author in thefamous_quotes
table. It can return its primary key (ID). - Populate the
get_quotes_by_author
method fromDBService
class to make aSELECT
query and return all quotes with a received author. - Populate the
get_quotes
method fromDBService
class to return all the quotes fromfamous_quotes
table. - Populate the
load_data
method fromDBService
class to insert in your database quotes from thedata.csv
file. You can read the file line by line or using the DictReader. Also, you can check the inserted data using a MySQL client, or by using theget_quotes
method you have written earlier. - Create a new route to edit the quote. You can use the same template
add.html
, just switch its logic using logic conditionsif
. - In the
add
view, after the form is submitted, insert the data using theinsert_quote
method you made earlier. Optionally, after a quote is submitted, redirect the user to the edit form/view. You can use the primaryKey(ID) from DB to form the URL (returned frominsert_quote
). - List all the existing quotes on the main page (the root route
/
). Here you can use theget_quotes
method made earlier. You can add the edit feature as a button on each quote using its ID to form the link. Make the url dynamic using something like/edit/{id}
.
Extra
- Drop you current DB scheme and make 2 tables,
author
andquote
. Don't forget to add a one-to-many relation between the tables Check the 1-M example, to let an author have more than one quote linked to him. - Make a method
get_or_create_author
which searches in theauthor
table for a provided name and returns the ID if exists. Otherwise, will create an author and will return the new ID. - Make a new method that inserts one quote and use the method
get_or_create_author
to get an ID for the author. Use the author's ID to add the quote. In this way, you'll link quotes with the same author to a single author. - Load the CSV data by using the recently created methods. Now you'll have authors with multiple quotes linked. A real one-to-many relationship.
- Now you can list quotes by author using the
JOIN
statement. Change the following query accordingly to your case:
SELECT *
FROM Parent P
INNER JOIN Child C
ON C.ParentId = P.ParentId
The above query will join data from both tables, in our case author
and quote
(SQL Joins). An extra WHERE
clause at the end will help you filter by a specific ID.
16. Make a new route and view to list all the authors with an href
URL to something like /author/{id}/quotes
.
17. Create a method for the route mentioned before /author/{id}/quotes
which will return a page with the author's quotes. Use the SQL JOIN query presented above.
As an intro, the simplest way to do that is by using a decorator. Before executing the route method, the decorator will check if the user is authenticated. Flask's documentation provides an example for auth decorator.
In our case, we will check the user authentication by having the flag logged_in
to True
in his session.
- Copy the provided decorator to the
app.py
file and change itsif
condition to check if the flaglogged_in
is True in the session dict. Examplesession["logged_in"] is True
. FYI make sure the key exists first. - Make a new route for login that accepts
GET
andPOST
requests. Handle the POST request by checking over some dummy data if the submitted data form match, and if it does, set the flaglogged_in
toTrue
in the session and redirect the user somewhere. Otherwise, return an error. On the GET request, return thelogin.html
template. You can add an extra argument for possible errors and render it in template if is not None. - Make a new route for logout action that will remove the
logged_in
key, or set a False value to it. This way, the auth decorator will not let the user access protected routes anymore. - Use the session to store everything you want related to the user (e.g. name) and access it from templates. You can add a simple HTML menu, where the Login button will be present if the user is not logged in. When he will be logged in, the login button will be replaced by a logout one and his username will be displayed in the same menu.
- Use the
login_required
decorator to protect some routes.
- fix typo
- Add edit view for a quote listed in the main page using the same
add.html
template also, make the url dynamic using something like/edit/{id}
. - db relationship (2 tables)
- auth
- logging (directly to a separated db table)
- bigger dataset to load (multithreading)