Skip to content

Commit 0a29dbf

Browse files
committed
release v1
1 parent 1e60178 commit 0a29dbf

20 files changed

Lines changed: 376 additions & 0 deletions

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
debug*
2+
*venv
3+
**/.DS_Store
4+
.pytest_cache
5+
.coverage
6+
__pycache__

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Welcome to Giga!
2+
3+
Giga - the gig booking site that'll be _huge_.
4+
5+
## Installation
6+
7+
* Clone this repository locally, `cd` into it
8+
* `python3 -m venv giga_venv`
9+
* `source giga_venv/bin/activate`
10+
* `pip install -r requirements.txt`
11+
* Create the databases specified in `lib/database_connection.py` - `giga` and `giga_test`
12+
* Seed the production database e.g. `psql -h 127.0.0.1 giga -f seeds/test_gigs.sql -f seeds/test_bookings.sql`
13+
* Run the server with `python app.py`

app.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import os
2+
from flask import Flask, request, render_template, redirect, url_for
3+
from lib.database_connection import get_flask_database_connection
4+
from lib.gig_repository import GigRepository
5+
from lib.booking_repository import BookingRepository
6+
7+
app = Flask(__name__)
8+
9+
@app.route('/home', methods=['GET'])
10+
def get_home():
11+
return render_template('home.html')
12+
13+
@app.route('/about', methods=['GET'])
14+
def get_about():
15+
return render_template('about.html')
16+
17+
@app.route('/gigs', methods=['GET'])
18+
def get_gigs():
19+
connection = get_flask_database_connection(app)
20+
repo = BookingRepository(connection)
21+
gig_booked_ids = [booking.gig_id for booking in repo.get_bookings()]
22+
repo = GigRepository(connection)
23+
gigs = repo.all()
24+
gig_ids = [gig.id for gig in gigs]
25+
booked_percentage = int(len(set(gig_booked_ids)) / len(set(gig_ids)) * 100)
26+
return render_template('gigs.html', gigs=gigs, number_bookings=booked_percentage)
27+
28+
@app.route('/login', methods=['GET'])
29+
def get_login():
30+
return render_template('login.html')
31+
32+
@app.route('/logout', methods=['GET'])
33+
def get_logout():
34+
return render_template('logout.html')
35+
36+
@app.route('/account', methods=['GET'])
37+
def get_account():
38+
connection = get_flask_database_connection(app)
39+
repo = BookingRepository(connection)
40+
bookings = repo.get_bookings()
41+
repo = GigRepository(connection)
42+
gigs = repo.all()
43+
booking_details = []
44+
for booking in bookings:
45+
ticket_text = f"{booking.ticket_count} tickets" if booking.ticket_count > 1 else f"{booking.ticket_count} ticket"
46+
booking_details.append({
47+
"ticket_count": ticket_text,
48+
"gig": repo.get_by_id(booking.gig_id)
49+
})
50+
return render_template('account.html', booking_details=booking_details)
51+
52+
@app.route('/tcs', methods=['GET'])
53+
def get_tcs():
54+
return render_template('tcs.html')
55+
56+
if __name__ == '__main__':
57+
app.run(debug=True, port=int(os.environ.get('PORT', 5001)))

lib/__init__.py

Whitespace-only changes.

lib/booking.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import datetime
2+
3+
class Booking:
4+
def __init__(self, id, dt, gig_id, user_id, ticket_count):
5+
self.id = id
6+
7+
# input expected to be in format "%Y-%m-%d %H:%M"
8+
# should be stored as datetime.datetime()
9+
# `.strftime("%Y-%m-%d %H:%M")` - for nice formatting
10+
if type(dt) == str:
11+
self.datetime = datetime.datetime.strptime(dt, "%Y-%m-%d %H:%M")
12+
else:
13+
self.datetime = dt
14+
15+
self.gig_id = gig_id
16+
self.user_id = user_id
17+
self.ticket_count = ticket_count

lib/booking_repository.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from lib.booking import Booking
2+
3+
class BookingRepository:
4+
def __init__(self, connection):
5+
self._connection = connection
6+
7+
def get_bookings(self):
8+
rows = self._connection.execute('SELECT * FROM bookings')
9+
bookings = []
10+
for row in rows:
11+
bookings.append(Booking(row["id"], row["datetime"], row["gig_id"], row["user_id"], row["ticket_count"]))
12+
return bookings

lib/database_connection.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import os, psycopg
2+
from flask import g
3+
from psycopg.rows import dict_row
4+
5+
class DatabaseConnection:
6+
DEV_DATABASE_NAME = "giga"
7+
TEST_DATABASE_NAME = "giga_test"
8+
9+
def __init__(self, test_mode=False):
10+
self.test_mode = test_mode
11+
12+
def connect(self):
13+
try:
14+
self.connection = psycopg.connect(
15+
f"postgresql://localhost/{self._database_name()}",
16+
row_factory=dict_row)
17+
except psycopg.OperationalError:
18+
raise Exception(f"Couldn't connect to the database {self._database_name()}! " \
19+
f"Did you create it using `createdb {self._database_name()}`?")
20+
21+
def seed(self, sql_filename):
22+
self._check_connection()
23+
if not os.path.exists(sql_filename):
24+
raise Exception(f"File {sql_filename} does not exist")
25+
with self.connection.cursor() as cursor:
26+
cursor.execute(open(sql_filename, "r").read())
27+
self.connection.commit()
28+
29+
def execute(self, query, params=[]):
30+
self._check_connection()
31+
with self.connection.cursor() as cursor:
32+
cursor.execute(query, params)
33+
if cursor.description is not None:
34+
result = cursor.fetchall()
35+
else:
36+
result = None
37+
self.connection.commit()
38+
return result
39+
40+
CONNECTION_MESSAGE = '' \
41+
'DatabaseConnection.exec_params: Cannot run a SQL query as ' \
42+
'the connection to the database was never opened. Did you ' \
43+
'make sure to call first the method DatabaseConnection.connect` ' \
44+
'in your app.py file (or in your tests)?'
45+
46+
def _check_connection(self):
47+
if self.connection is None:
48+
raise Exception(self.CONNECTION_MESSAGE)
49+
50+
def _database_name(self):
51+
if self.test_mode:
52+
return self.TEST_DATABASE_NAME
53+
else:
54+
return self.DEV_DATABASE_NAME
55+
56+
def get_flask_database_connection(app):
57+
if not hasattr(g, 'flask_database_connection'):
58+
g.flask_database_connection = DatabaseConnection(
59+
test_mode=((os.getenv('APP_ENV') == 'test') or (app.config['TESTING'] == True))
60+
)
61+
g.flask_database_connection.connect()
62+
return g.flask_database_connection

lib/gig.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import datetime
2+
3+
class Gig:
4+
def __init__(self, id, dt, band, venue, location, postcode):
5+
self.id = id
6+
7+
# input expected to be in format "%Y-%m-%d %H:%M"
8+
# should be stored as datetime.datetime()
9+
# `.strftime("%Y-%m-%d %H:%M")` - for nice formatting
10+
if type(dt) == str:
11+
self.datetime = datetime.datetime.strptime(dt, "%Y-%m-%d %H:%M")
12+
else:
13+
self.datetime = dt
14+
15+
self.band = band
16+
self.venue = venue
17+
18+
# City/Town/etc.
19+
self.location = location
20+
21+
# for maps lookup
22+
self.postcode = postcode

lib/gig_repository.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from lib.gig import Gig
2+
3+
class GigRepository:
4+
def __init__(self, connection):
5+
self._connection = connection
6+
7+
def all(self):
8+
rows = self._connection.execute('SELECT * FROM gigs')
9+
gigs = []
10+
for row in rows:
11+
gigs.append(Gig(row["id"], row["datetime"], row["band"], row["venue"], row["location"], row["postcode"]))
12+
return gigs
13+
14+
def get_by_id(self, gig_id):
15+
rows = self._connection.execute('SELECT * FROM gigs WHERE id = %s', [gig_id])
16+
row = rows[0]
17+
return Gig(row["id"], row["datetime"], row["band"], row["venue"], row["location"], row["postcode"])

requirements.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
blinker==1.9.0
2+
click==8.2.1
3+
Flask==3.1.1
4+
Flask-Login==0.6.3
5+
greenlet==3.2.2
6+
iniconfig==2.1.0
7+
itsdangerous==2.2.0
8+
Jinja2==3.1.6
9+
MarkupSafe==3.0.2
10+
packaging==25.0
11+
playwright==1.52.0
12+
pluggy==1.6.0
13+
psutil==7.0.0
14+
psycopg==3.2.9
15+
pyee==13.0.0
16+
pytest==8.3.5
17+
pytest-xprocess==1.0.2
18+
typing_extensions==4.13.2
19+
Werkzeug==3.1.3

0 commit comments

Comments
 (0)