Skip to content

Commit 478aa55

Browse files
committed
Add day 88 - Coffee shop project - Flask, jinja, flask-wtf, sqlalchemy
1 parent c7f1fa7 commit 478aa55

9 files changed

Lines changed: 304 additions & 0 deletions

File tree

day_88/main.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""
2+
On day 66, we create an API that serves data on cafes with wifi and good coffee. Today, you're going to use the data from that
3+
project to build a fully-fledged website to display the information.
4+
Included in this assignment is an SQLite database called cafes.db that lists all the cafe data.
5+
6+
Using this database and what you learnt about REST APIs and web development, create a website that uses this data.
7+
It should display the cafes, but it could also allow people to add new cafes or delete cafes.
8+
For example, this startup in London has a website that does exactly this:
9+
https://laptopfriendly.co/london
10+
"""
11+
12+
from flask import Flask, redirect, url_for, render_template, request
13+
from flask_sqlalchemy import SQLAlchemy
14+
import os
15+
16+
# Create a new instance of the app
17+
app = Flask(__name__)
18+
19+
# List of all cafes
20+
all_cafes = []
21+
22+
# Create the connection with the database
23+
db = SQLAlchemy()
24+
25+
# Path to the database
26+
db_path = os.path.join(os.path.dirname(__file__), "cafes.db")
27+
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}"
28+
db.init_app(app)
29+
30+
# Create a new object called Model from the class Cafes.
31+
class Cafe(db.Model):
32+
id = db.Column(db.Integer, primary_key=True)
33+
name = db.Column(db.String(250), unique=True, nullable=False)
34+
map_url = db.Column(db.String(500), nullable=False)
35+
img_url = db.Column(db.String(500), nullable=False)
36+
location = db.Column(db.String(250), nullable=False)
37+
seats = db.Column(db.String(250), nullable=False)
38+
has_toilet = db.Column(db.Boolean, nullable=False)
39+
has_wifi = db.Column(db.Boolean, nullable=False)
40+
has_sockets = db.Column(db.Boolean, nullable=False)
41+
can_take_calls = db.Column(db.Boolean, nullable=False)
42+
coffee_price = db.Column(db.String(250), nullable=True)
43+
44+
with app.app_context():
45+
db.create_all()
46+
47+
@app.route("/")
48+
def home():
49+
return render_template("index.html")
50+
51+
@app.route("/shops")
52+
def coffee_shops():
53+
all_cafes = db.session.execute(db.select(Cafe).order_by(Cafe.name)).scalars()
54+
print(all_cafes)
55+
return render_template("coffee_shops.html", all_cafes=all_cafes)
56+
57+
@app.route("/delete", methods=["GET", "POST"])
58+
def delete_coffee():
59+
if request.method == "GET":
60+
return render_template("delete.html")
61+
if request.method == "POST":
62+
id = request.form.get("coffee_id")
63+
coffee_to_delete = db.get_or_404(Cafe, id)
64+
coffee_name = coffee_to_delete.name
65+
db.session.delete(coffee_to_delete)
66+
db.session.commit()
67+
return redirect(url_for("delete_confirmation", coffee_name=coffee_name))
68+
69+
@app.route("/suggest_coffee", methods=["GET", "POST"])
70+
def suggest_coffee():
71+
if request.method == "POST":
72+
new_coffee_name = request.form.get("name")
73+
new_coffee = Cafe(
74+
name=new_coffee_name,
75+
map_url=request.form.get("map_url"),
76+
img_url=request.form.get("img_url"),
77+
location=request.form.get("location"),
78+
has_sockets=1 if request.form.get("sockets", "") else 0,
79+
has_toilet=1 if request.form.get("toilets", "") else 0,
80+
has_wifi=1 if request.form.get("wifi", "") else 0,
81+
can_take_calls=1 if request.form.get("can_take_calls", "") else 0,
82+
seats=request.form.get("seats"),
83+
coffee_price=f"£{request.form.get('coffee_price')}",
84+
)
85+
db.session.add(new_coffee)
86+
db.session.commit()
87+
return redirect(url_for("saved", new_coffee_name=new_coffee_name))
88+
return render_template("suggest.html")
89+
90+
@app.route("/confirmation/<coffee_name>")
91+
def delete_confirmation(coffee_name):
92+
return render_template("delete_confirmation.html", coffee_name=coffee_name)
93+
94+
@app.route("/<new_coffee_name>")
95+
def saved(new_coffee_name):
96+
return render_template("saved_confirmation.html", new_coffee_name=new_coffee_name)
97+
98+
if __name__ == "__main__":
99+
app.run(debug=True, port=5000)

day_88/static/css/stiles.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.img-header {
2+
width: 100%;
3+
height: auto;
4+
}

day_88/templates/base.html

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>{% block title %} Mocha Maps {% endblock %}</title>
8+
</head>
9+
<body>
10+
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
11+
<a class="navbar-brand" href="{{url_for('home')}}">MochaMaps</a>
12+
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
13+
<span class="navbar-toggler-icon"></span>
14+
</button>
15+
<div class="collapse navbar-collapse" id="navbarNav">
16+
<ul class="navbar-nav">
17+
<li class="nav-item">
18+
<a class="nav-link" href="{{url_for('coffee_shops', all_cafes = all_cafes)}}">Coffee Shops</a>
19+
</li>
20+
<li class="nav-item">
21+
<a class="nav-link" href="{{url_for('suggest_coffee')}}">Suggest</a>
22+
</li>
23+
<li class="nav-item">
24+
<a class="nav-link" href="{{url_for('delete_coffee')}}">Remove</a>
25+
</li>
26+
</ul>
27+
</div>
28+
</nav>
29+
30+
{% block content %}
31+
<div class="container">
32+
<h1> Coffee Shops </h1>
33+
<p></p>
34+
</div>
35+
{%endblock%}
36+
37+
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
38+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
39+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
40+
</body>
41+
</html>

day_88/templates/coffee_shops.html

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{% extends "base.html" %}
2+
{% block title %} Home {% endblock %}
3+
4+
{% block content %}
5+
<div class="container mt-4">
6+
<h1>Recorded coffee shops</h1>
7+
<p>
8+
Discover the perfect spots to enjoy a great cup of coffee while you work away on your laptop. The listed coffee shops offers comfortable seating, convenient charging stations, and free Wi-Fi, ensuring a productive ambiance without the need to ask for a password. Whether you're meeting deadlines or just taking a moment to relax, find your next work-friendly café with ease.
9+
</p>
10+
<div class="row mt-4">
11+
{% for coffee in all_cafes %}
12+
<div class="col-md-3 mb-4"> <!-- Add mb-4 for margin bottom -->
13+
<div class="card h-100 shadow-sm d-flex flex-column"> <!-- Add h-100 and d-flex flex-column -->
14+
<img src="{{ coffee.img_url }}" class="card-img-top" alt="{{ coffee.name }}">
15+
<div class="card-body d-flex flex-column"> <!-- Add d-flex flex-column -->
16+
<h4 class="card-title">{{ coffee.name }}</h4>
17+
<ul class="list-unstyled flex-grow-1"> <!-- Add flex-grow-1 -->
18+
<li> 🏷️ Shop id: {{coffee.id}}</li>
19+
<li>📍 {{ coffee.location }}</li>
20+
{% if coffee.has_sockets == 1 %}
21+
<li> 🔌 charging station </li>
22+
{% endif %}
23+
<li> ☕ $: {{ coffee.coffee_price }}</li>
24+
<li> 🪑 seats: {{ coffee.seats }}</li>
25+
{% if coffee.has_toilet == 1 %}
26+
<li> 🚽 toilets</li>
27+
{% endif %}
28+
{% if coffee.has_wifi == 1 %}
29+
<li> 🌐 wifi</li>
30+
{% endif %}
31+
</ul>
32+
<div class="mt-auto"> <!-- This ensures buttons align to the bottom -->
33+
<a href="{{ coffee.map_url }}" target="_blank" class="btn btn-sm btn-outline-secondary">Open in maps</a>
34+
</div>
35+
</div>
36+
</div>
37+
</div>
38+
{% endfor %}
39+
</div>
40+
</div>
41+
{% endblock %}

day_88/templates/delete.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{%extends "base.html"%}
2+
{%block title%} Suggest a Coffee Shop {%endblock%}
3+
{% block content%}
4+
<div class = "container mt-4">
5+
<h1>Remove a Listing from Our Coffee Shop Directory</h1>
6+
<p>If you're uncertain about the coffee shop's ID, please refer to the <a href="{{url_for('coffee_shops')}}">Coffee Shops</a> section of this website. Below, an image is provided to illustrate where you can locate the ID on each shop's information card.</p>
7+
<div class = "row">
8+
<div class = "col-md-6">
9+
<form method="post">
10+
Please introduce the id of the coffee shop that you would like to delete: <br>
11+
<input class = "mt-2" coffee_id="coffee_id" name = "coffee_id"><br>
12+
<input class = "mt-2" type="submit" value="Delete coffee shop">
13+
</form>
14+
</div>
15+
<div class = "col-md-6">
16+
<img src="./static/assets/coffee_shop_id.png">
17+
</div>
18+
</div>
19+
20+
</div>
21+
{% endblock %}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{%extends "base.html"%}
2+
{%block title%} Coffee Shop Deleted {%endblock%}
3+
{% block content%}
4+
<div class = "container mt-4">
5+
<h1>Coffee Shop Deleted Successfully</h1>
6+
<p>
7+
Thank you for your contribution to our community!<b>"{{coffee_name}}"</b> has been successfully removed from our listings. Your input helps us keep our information up-to-date and relevant. If there's anything else you'd like to do, here are a few suggestions:</p>
8+
<ul>
9+
<li><a href="{{url_for('home')}}">Return to the Homepage</a></li>
10+
<li><a href="{{url_for('coffee_shops')}}">View More Coffee Shops</a></li>
11+
<li><a href="{{url_for('suggest_coffee')}}">Add Another Coffee Shop</a></li>
12+
</ul>
13+
<p>We appreciate your help in making our community better!</p>
14+
</div>
15+
{% endblock %}

day_88/templates/index.html

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{%extends "base.html"%}
2+
{%block title%} Home {%endblock%}
3+
{% block content%}
4+
5+
<div class = "container mt-4">
6+
<h1>MochaMaps: Your Personal Guide to Coffee Shop Discovery</h1>
7+
<div class = "row">
8+
<div class = "col-md-12">
9+
<img class = "img-header" src="https://images.unsplash.com/photo-1545239351-ef35f43d514b?q=80&w=1000&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D">
10+
</div>
11+
<div class = "col-md-6 mt-4">
12+
<p>
13+
Welcome to MochaMaps, where every sip comes with a story. Created by a band of coffee enthusiasts, MochaMaps is a tribute to coffee culture around the globe. Our mission is simple: to connect coffee lovers with the perfect café experience. As we journeyed from city to city, we found ourselves documenting every aspect of the coffee shops we visited – from the richness of the roast to the comfort of the seating. MochaMaps evolved from a personal quest to a shared resource for discovering unique coffee spots that cater not just to your taste buds but also to your need for a welcoming space to relax, work, or meet friends.
14+
</p>
15+
</div>
16+
<div class = "col-md-6 mt-4">
17+
<p>
18+
We believe that a great coffee shop is more than just a place to grab a caffeine fix; it's a hub of community, creativity, and connection. Each entry on MochaMaps is handpicked for its character, quality, and the little extras that make a café stand out from the crowd. Whether you're in search of the quiet corner with the best Wi-Fi, or the bustling spot with space for friends, MochaMaps is your compass to finding the perfect place to fuel your day and indulge your love for coffee. Join our community of café hunters, and let MochaMaps guide you to your next coffee adventure.
19+
</p>
20+
</div>
21+
</div>
22+
<div class = "row mt-5">
23+
<div class = "col-md-4 mt-4">
24+
<h4>
25+
📫 Get in contact
26+
</h4>
27+
<ul>
28+
29+
<li>+44(0) 7899 866 210</li>
30+
</ul>
31+
</div>
32+
</div>
33+
</div>
34+
{% endblock %}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{%extends "base.html"%}
2+
{%block title%} Coffee Shop Add {%endblock%}
3+
{% block content%}
4+
<div class = "container mt-4">
5+
<h1>Coffee Shop Added Successfully</h1>
6+
<p>
7+
Thank you for your contribution to our community!<b>"{{new_coffee_name}}"</b> has been successfully added from our listings. Your input helps us keep our information up-to-date and relevant. If there's anything else you'd like to do, here are a few suggestions:</p>
8+
<ul>
9+
<li><a href="{{url_for('home')}}">Return to the Homepage</a></li>
10+
<li><a href="{{url_for('coffee_shops')}}">View More Coffee Shops</a></li>
11+
<li><a href="{{url_for('suggest_coffee')}}">Add Another Coffee Shop</a></li>
12+
</ul>
13+
<p>We appreciate your help in making our community better!</p>
14+
</div>
15+
{% endblock %}

day_88/templates/suggest.html

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{%extends "base.html"%}
2+
{%block title%} Suggest a Coffee Shop {%endblock%}
3+
{% block content%}
4+
<div class="container mt-4">
5+
<h1>Record a new coffee shops</h1>
6+
<p> Add a new coffee shop listing to our MochaMaps database. Please ensure all information is accurate before submitting. </p>
7+
<form action="{{ url_for('suggest_coffee') }}" method="post">
8+
<label for="name"> Coffee Shop Name </label><br>
9+
<input class = "mb-4" type="text" name="name" placeholder="The Coffee Shop" required="required" /><br>
10+
11+
<label for="location"> Location </label><br>
12+
<input class = "mb-4" type="location" name="location" placeholder="King's Cross" required="required" /><br>
13+
14+
<label for="img_url"> Image URL </label><br>
15+
<input class = "mb-4" type="url" name="img_url" placeholder="https://mariabalos.art/" required="required" /><br>
16+
17+
<label for="img_url"> Google Maps URL </label><br>
18+
<input class = "mb-4" type="url" name="map_url" placeholder="https://maps.app.goo.gl/hgTJJqdWUVou4Fuf7" required="required" /><br>
19+
20+
<label for="img_url"> Coffee price (£) </label><br>
21+
<input class = "mb-4" type="text" name="coffee_price" placeholder="2.4" required="required" /><br>
22+
23+
<label for="img_url"> Number of seats (range)</label><br>
24+
<input class = "mb-4" type="text" name="seats" placeholder="10-20" required="required" /><br>
25+
26+
<input class = "mb-4" type="checkbox" autocomplete="off" name="sockets" value="True"> Sockets
27+
<input class = "ml-5 mb-4" type="checkbox" autocomplete="off" name="toilet" value="True"> Toilet
28+
<input class = "ml-5 mb-4" type="checkbox" autocomplete="off" name="wifi" value="True"> Wifi
29+
<input class = "ml-5 mb-4" type="checkbox" autocomplete="off" name="can_take_calls" value="True"> You can take calls
30+
<br>
31+
<button type="submit" class="btn btn-primary mt-4">RECORD THE COFFEE SHOP</button>
32+
</form>
33+
</div>
34+
{% endblock %}

0 commit comments

Comments
 (0)