Skip to content

Commit 936ee7b

Browse files
committed
Add code for REST API frontend
1 parent 043e2a5 commit 936ee7b

23 files changed

+1072
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Build a Frontend for a REST API
2+
3+
This repository contains code related to the tutorial on [building a frontend for a REST API](https://realpython.com/build-a-rest-api-frontend/).
4+
5+
Create and activate a [virtual environment](https://realpython.com/python-virtual-environments-a-primer/), then install the necessary dependencies:
6+
7+
```sh
8+
$ python -m venv venv
9+
$ source venv/bin/activate
10+
(venv) $ python -m pip install -r requirements.txt
11+
```
12+
13+
Then you can navigate into the folder `source_code_final/` and create a new database:
14+
15+
```sh
16+
(venv) $ cd source_code_final
17+
(venv) $ python build_databas.py
18+
```
19+
20+
After building the database, you can start the Flask server:
21+
22+
```sh
23+
(venv) $ python app.py
24+
```
25+
26+
When the Flask server is running, you can visit the frontend on `http://localhost:8000` and the API documentation on `http://localhost:8000/api/ui`.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
attrs==22.1.0
2+
certifi==2022.9.24
3+
charset-normalizer==2.1.1
4+
click==8.1.3
5+
clickclick==20.10.2
6+
connexion==2.14.1
7+
Flask==2.2.2
8+
flask-marshmallow==0.14.0
9+
Flask-SQLAlchemy==3.0.0
10+
idna==3.4
11+
inflection==0.5.1
12+
itsdangerous==2.1.2
13+
Jinja2==3.1.2
14+
jsonschema==4.16.0
15+
MarkupSafe==2.1.1
16+
marshmallow==3.18.0
17+
marshmallow-sqlalchemy==0.28.1
18+
packaging==21.3
19+
pyparsing==3.0.9
20+
pyrsistent==0.18.1
21+
PyYAML==6.0
22+
requests==2.28.1
23+
six==1.16.0
24+
SQLAlchemy==1.4.41
25+
swagger-ui-bundle==0.0.9
26+
urllib3==1.26.12
27+
Werkzeug==2.2.2
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from flask import render_template
2+
import config
3+
from models import Person
4+
5+
app = config.connex_app
6+
app.add_api("swagger.yml")
7+
8+
9+
@app.route("/")
10+
def home():
11+
people = Person.query.all()
12+
return render_template("home.html", people=people)
13+
14+
15+
if __name__ == "__main__":
16+
app.run(host="0.0.0.0", port=8000, debug=True)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from datetime import datetime
2+
from config import app, db
3+
from models import Person, Note
4+
5+
PEOPLE_NOTES = [
6+
{
7+
"lname": "Fairy",
8+
"fname": "Sugar",
9+
"notes": [
10+
("Cool, a mini-blogging application!", "2022-01-06 22:17:54"),
11+
("This could be useful", "2022-01-08 22:17:54"),
12+
("Well, sort of useful", "2022-03-06 22:17:54"),
13+
],
14+
},
15+
{
16+
"lname": "Ruprecht",
17+
"fname": "Knecht",
18+
"notes": [
19+
(
20+
"I'm going to make really profound observations",
21+
"2022-01-07 22:17:54",
22+
),
23+
(
24+
"Maybe they'll be more obvious than I thought",
25+
"2022-02-06 22:17:54",
26+
),
27+
],
28+
},
29+
{
30+
"lname": "Bunny",
31+
"fname": "Easter",
32+
"notes": [
33+
("Has anyone seen my Easter eggs?", "2022-01-07 22:47:54"),
34+
("I'm really late delivering these!", "2022-04-06 22:17:54"),
35+
],
36+
},
37+
]
38+
39+
with app.app_context():
40+
db.drop_all()
41+
db.create_all()
42+
for data in PEOPLE_NOTES:
43+
new_person = Person(lname=data.get("lname"), fname=data.get("fname"))
44+
for note in data.get("notes"):
45+
content, timestamp = note
46+
new_person.notes.append(
47+
Note(
48+
content=content,
49+
timestamp=datetime.strptime(
50+
timestamp, "%Y-%m-%d %H:%M:%S"
51+
),
52+
)
53+
)
54+
db.session.add(new_person)
55+
56+
db.session.commit()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import os
2+
import connexion
3+
from flask_sqlalchemy import SQLAlchemy
4+
from flask_marshmallow import Marshmallow
5+
6+
basedir = os.path.abspath(os.path.dirname(__file__))
7+
connex_app = connexion.App(__name__, specification_dir=basedir)
8+
9+
app = connex_app.app
10+
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:////" + os.path.join(
11+
basedir, "people.db"
12+
)
13+
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
14+
15+
db = SQLAlchemy(app)
16+
ma = Marshmallow(app)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from datetime import datetime
2+
from marshmallow_sqlalchemy import fields
3+
from config import db, ma
4+
5+
6+
class Note(db.Model):
7+
__tablename__ = "note"
8+
id = db.Column(db.Integer, primary_key=True)
9+
person_id = db.Column(db.Integer, db.ForeignKey("person.id"))
10+
content = db.Column(db.String, nullable=False)
11+
timestamp = db.Column(
12+
db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
13+
)
14+
15+
16+
class NoteSchema(ma.SQLAlchemyAutoSchema):
17+
class Meta:
18+
model = Note
19+
load_instance = True
20+
sqla_session = db.session
21+
include_fk = True
22+
23+
24+
class Person(db.Model):
25+
__tablename__ = "person"
26+
id = db.Column(db.Integer, primary_key=True)
27+
lname = db.Column(db.String(32))
28+
fname = db.Column(db.String(32))
29+
timestamp = db.Column(
30+
db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
31+
)
32+
notes = db.relationship(
33+
"Note",
34+
backref="person",
35+
cascade="all, delete, delete-orphan",
36+
single_parent=True,
37+
order_by="desc(Note.timestamp)",
38+
)
39+
40+
41+
class PersonSchema(ma.SQLAlchemyAutoSchema):
42+
class Meta:
43+
model = Person
44+
load_instance = True
45+
sqla_session = db.session
46+
include_relationships = True
47+
48+
notes = fields.Nested(NoteSchema, many=True)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from flask import make_response, abort
2+
3+
from config import db
4+
from models import (
5+
Note,
6+
NoteSchema,
7+
Person,
8+
)
9+
10+
11+
def read_one(note_id):
12+
note = Note.query.get(note_id)
13+
14+
if note is not None:
15+
note_schema = NoteSchema()
16+
return note_schema.dump(note)
17+
else:
18+
abort(404, f"Note with ID {note_id} not found")
19+
20+
21+
def update(note_id, note):
22+
existing_note = Note.query.get(note_id)
23+
24+
if existing_note:
25+
schema = NoteSchema()
26+
update_note = schema.load(note, session=db.session)
27+
existing_note.content = update_note.content
28+
db.session.merge(existing_note)
29+
db.session.commit()
30+
return schema.dump(existing_note), 201
31+
else:
32+
abort(404, f"Note with ID {note_id} not found")
33+
34+
35+
def delete(note_id):
36+
existing_note = Note.query.get(note_id)
37+
38+
if existing_note:
39+
db.session.delete(existing_note)
40+
db.session.commit()
41+
return make_response(f"{note_id} successfully deleted", 200)
42+
else:
43+
abort(404, f"Note with ID {note_id} not found")
44+
45+
46+
def create(note):
47+
person_id = note.get("person_id")
48+
person = Person.query.get(person_id)
49+
50+
if person:
51+
schema = NoteSchema()
52+
new_note = schema.load(note, session=db.session)
53+
person.notes.append(new_note)
54+
db.session.commit()
55+
return schema.dump(new_note), 201
56+
else:
57+
abort(404, f"Person not found for ID: {person_id}")
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from flask import make_response, abort
2+
3+
from config import db
4+
from models import (
5+
Person,
6+
PersonSchema,
7+
)
8+
9+
10+
def read_all():
11+
people = Person.query.all()
12+
person_schema = PersonSchema(many=True)
13+
return person_schema.dump(people)
14+
15+
16+
def create(person):
17+
schema = PersonSchema()
18+
new_person = schema.load(person, session=db.session)
19+
db.session.add(new_person)
20+
db.session.commit()
21+
return schema.dump(new_person), 201
22+
23+
24+
def read_one(person_id):
25+
person = Person.query.get(person_id)
26+
27+
if person is not None:
28+
person_schema = PersonSchema()
29+
return person_schema.dump(person)
30+
else:
31+
abort(404, f"Person with ID {person_id} not found")
32+
33+
34+
def update(person_id, person):
35+
existing_person = Person.query.get(person_id)
36+
37+
if existing_person:
38+
schema = PersonSchema()
39+
update_person = schema.load(person, session=db.session)
40+
existing_person.fname = update_person.fname
41+
db.session.merge(existing_person)
42+
db.session.commit()
43+
return schema.dump(existing_person), 201
44+
else:
45+
abort(404, f"Person with ID {person_id} not found")
46+
47+
48+
def delete(person_id):
49+
existing_person = Person.query.get(person_id)
50+
51+
if existing_person:
52+
db.session.delete(existing_person)
53+
db.session.commit()
54+
return make_response(f"{person_id} successfully deleted", 200)
55+
else:
56+
abort(404, f"Person with ID {person_id} not found")
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
.note-control-card {
2+
text-align: right;
3+
}
4+
5+
.note-control {
6+
text-align: left;
7+
}
8+
9+
.note-create-card {
10+
background-color: var(--secondary-color);
11+
padding: 1em;
12+
}
13+
14+
.note-create-card input,
15+
.note-control-card input {
16+
width: 100%;
17+
}
18+
19+
.note-list {
20+
list-style: none;
21+
padding-left: 0;
22+
}
23+
24+
.note-card {
25+
background-color: blanchedalmond;
26+
padding: 1em;
27+
margin: 0.6em 0;
28+
}
29+
30+
.note-content {
31+
padding: 0.3em 0;
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
.person-control-card {
2+
text-align: right;
3+
}
4+
5+
.person-control {
6+
text-align: left;
7+
}
8+
9+
.person-create-card {
10+
margin-right: 1em;
11+
}
12+
13+
.person-create-card,
14+
.person-control-card.editing {
15+
background-color: var(--secondary-color);
16+
padding: 1em;
17+
}
18+
19+
.person-create-card input,
20+
.person-control-card input {
21+
width: 100%;
22+
}
23+
24+
.people-list {
25+
margin-bottom: 1.3em;
26+
}
27+
28+
.person-card {
29+
border-left: 0.3em solid var(--main-color);
30+
padding: 0.3em 1em;
31+
margin: 1em 0;
32+
}

0 commit comments

Comments
 (0)