Uploading chapter 11 code examples
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for grades project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'grades.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Use the official lightweight Python image.
|
||||||
|
# https://hub.docker.com/_/python
|
||||||
|
FROM python:3.8-slim
|
||||||
|
|
||||||
|
# Allow statements and log messages to immediately appear in the Knative logs
|
||||||
|
ENV PYTHONUNBUFFERED True
|
||||||
|
|
||||||
|
# Copy local code to the container image.
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . ./
|
||||||
|
|
||||||
|
# Install production dependencies.
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
RUN pip install Flask gunicorn
|
||||||
|
|
||||||
|
# Run the web service on container startup. Here we use the gunicorn
|
||||||
|
# webserver, with one worker process and 8 threads.
|
||||||
|
# For environments with multiple CPU cores, increase the number of workers
|
||||||
|
# to be equal to the cores available.
|
||||||
|
# Timeout is set to 0 to disable the timeouts of the workers to allow Cloud Run to handle instance scaling.
|
||||||
|
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 api_app:app
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# api_app: REST API for student resource
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask import Flask
|
||||||
|
from flask_restful import Resource, Api, reqparse
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
api = Api(app)
|
||||||
|
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///student.db'
|
||||||
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument('name', type=str)
|
||||||
|
parser.add_argument('grade', type=str)
|
||||||
|
|
||||||
|
|
||||||
|
class Student(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(80), nullable=False)
|
||||||
|
grade = db.Column(db.String(20), nullable=True)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{'id':{self.id}, 'name':{self.name},'grade':{self.grade}}"
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'name': self.name,
|
||||||
|
'grade': self.grade
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class StudentDao(Resource):
|
||||||
|
def get(self, student_id):
|
||||||
|
student = Student.query.filter_by(id=student_id).\
|
||||||
|
first_or_404(
|
||||||
|
description='Record with id={} is not available'.format(student_id))
|
||||||
|
return student.serialize()
|
||||||
|
|
||||||
|
def delete(self, student_id):
|
||||||
|
student = Student.query.filter_by(id=student_id).\
|
||||||
|
first_or_404(
|
||||||
|
description='Record with id={} is not available'.format(student_id))
|
||||||
|
db.session.delete(student)
|
||||||
|
db.session.commit()
|
||||||
|
return '', 204
|
||||||
|
|
||||||
|
def put(self, student_id):
|
||||||
|
student = Student.query.filter_by(id=student_id).first_or_404(
|
||||||
|
description='Record with id={} is not available'.format(student_id))
|
||||||
|
args = parser.parse_args()
|
||||||
|
name = args['name']
|
||||||
|
grade = args['grade']
|
||||||
|
if (name):
|
||||||
|
student.name = name
|
||||||
|
if (grade):
|
||||||
|
student.grade = grade
|
||||||
|
db.session.commit()
|
||||||
|
return student.serialize(), 200
|
||||||
|
|
||||||
|
|
||||||
|
class StudentListDao(Resource):
|
||||||
|
def get(self):
|
||||||
|
students = Student.query.all()
|
||||||
|
return [Student.serialize(student) for student in students]
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
args = parser.parse_args()
|
||||||
|
name = args['name']
|
||||||
|
grade = args['grade']
|
||||||
|
student = Student(name=name, grade=grade)
|
||||||
|
db.session.add(student)
|
||||||
|
db.session.commit()
|
||||||
|
return student.serialize(), 200
|
||||||
|
|
||||||
|
|
||||||
|
api.add_resource(StudentDao, '/students/<student_id>')
|
||||||
|
api.add_resource(StudentListDao, '/students')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# app.run(debug=True)
|
||||||
|
app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
aniso8601==9.0.1
|
||||||
|
attrs==21.2.0
|
||||||
|
certifi==2021.5.30
|
||||||
|
chardet==4.0.0
|
||||||
|
click==8.0.1
|
||||||
|
Flask==2.0.1
|
||||||
|
Flask-RESTful==0.3.9
|
||||||
|
flask-restplus==0.13.0
|
||||||
|
Flask-SQLAlchemy==2.5.1
|
||||||
|
greenlet==1.1.0
|
||||||
|
idna==2.10
|
||||||
|
itsdangerous==2.0.1
|
||||||
|
Jinja2==3.0.1
|
||||||
|
jsonschema==3.2.0
|
||||||
|
MarkupSafe==2.0.1
|
||||||
|
pyrsistent==0.17.3
|
||||||
|
pytz==2021.1
|
||||||
|
requests==2.25.1
|
||||||
|
six==1.16.0
|
||||||
|
SQLAlchemy==1.4.19
|
||||||
|
urllib3==1.26.6
|
||||||
|
utils-flask-sqlalchemy==0.1.4
|
||||||
|
Werkzeug==2.0.1
|
||||||
Binary file not shown.
@@ -0,0 +1,44 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||||
|
<script src="{{ url_for('static', filename='js/test.js') }}"></script>
|
||||||
|
<title> {% block title %} {% endblock title %} - Students</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="/">Students</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" aria-current="page" href="#">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#">About</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
{% endblock body %}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block title %} Home{% endblock title %}
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container my-3">
|
||||||
|
<h2>Add a Student</h2>
|
||||||
|
<form action="/" method="POST">
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="fname" class="form-label">First name</label>
|
||||||
|
<input type="text" class="form-control" name="fname" id="fname" aria-describedby="emailHelp">
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="lname" class="form-label">Last Name</label>
|
||||||
|
<input type="text" class="form-control" name="lname" id="lname" aria-describedby="emailHelp">
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="grade" class="form-label">Grade</label>
|
||||||
|
<input type="text" class="form-control" name="grade" id="grade">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-dark">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container my-3">
|
||||||
|
<h2>Students</h2>
|
||||||
|
{% if students|length == 0 %}
|
||||||
|
<div class="alert alert-dark" role="alert">
|
||||||
|
No student found. Add your first student now!
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">No</th>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">Grade</th>
|
||||||
|
<th scope="col">Building</th>
|
||||||
|
<th scope="col">Teacher</th>
|
||||||
|
<th scope="col">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{% for student in students %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{{loop.index}}</th>
|
||||||
|
<td>{{student.name}}</td>
|
||||||
|
<td>{{student.grade}}</td>
|
||||||
|
<td>{{student.building}}</td>
|
||||||
|
<td>{{student.teacher}}</td>
|
||||||
|
<td>
|
||||||
|
<a href="/update/{{student.id}}" type="button" class="btn btn-outline-dark btn-sm mx-1">Update</button>
|
||||||
|
<a href="/delete/{{student.id}}" type="button" class="btn btn-outline-dark btn-sm mx-1">Delete</button>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- Optional JavaScript; choose one of the two! -->
|
||||||
|
|
||||||
|
<!-- Option 1: Bootstrap Bundle with Popper -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<!-- Option 2: Separate Popper and Bootstrap JS -->
|
||||||
|
<!--
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.6.0/dist/umd/popper.min.js" integrity="sha384-KsvD1yqQ1/1+IA7gi3P0tyJcT3vR+NdBTt13hSJ2lnve8agRGXTTyNaBYmCR/Nwi" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.min.js" integrity="sha384-nsg8ua9HAw1y0W1btsyWgBklPnCUAFLuTMS2G72MMONqmOymq585AcH49TLBQObG" crossorigin="anonymous"></script>
|
||||||
|
-->
|
||||||
|
|
||||||
|
{% endblock body %}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block title %} Home{% endblock title %}
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<div class="container my-3">
|
||||||
|
<h2>Update Student</h2>
|
||||||
|
<form action="/update/{{student.id}}" method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="fname" class="form-label">First name</label>
|
||||||
|
<input type="text" class="form-control" name="fname" id="fname" value="{{fname}}" aria-describedby="emailHelp">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="lname" class="form-label">Last Name</label>
|
||||||
|
<input type="text" class="form-control" name="lname" id="lname" value="{{lname}}" aria-describedby="emailHelp">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="grade" class="form-label">Grade</label>
|
||||||
|
<input type="text" class="form-control" name="grade" id="grade" value="{{student.grade}}" >
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-dark">Update</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Optional JavaScript; choose one of the two! -->
|
||||||
|
|
||||||
|
<!-- Option 1: Bootstrap Bundle with Popper -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<!-- Option 2: Separate Popper and Bootstrap JS -->
|
||||||
|
<!--
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.6.0/dist/umd/popper.min.js" integrity="sha384-KsvD1yqQ1/1+IA7gi3P0tyJcT3vR+NdBTt13hSJ2lnve8agRGXTTyNaBYmCR/Nwi" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.min.js" integrity="sha384-nsg8ua9HAw1y0W1btsyWgBklPnCUAFLuTMS2G72MMONqmOymq585AcH49TLBQObG" crossorigin="anonymous"></script>
|
||||||
|
-->
|
||||||
|
|
||||||
|
{% endblock body %}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#webapp.py: interacting with business latyer via REST API
|
||||||
|
# for create, delete and list objects
|
||||||
|
from flask import Flask, render_template, redirect, request
|
||||||
|
import requests, json
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
#STUDENTS_MS = "http://localhost:8080/students"
|
||||||
|
STUDENTS_MS = "https://students2-wwzgfqvyaa-de.a.run.app/students"
|
||||||
|
|
||||||
|
GRADES_MS = "http://localhost:8000/grades"
|
||||||
|
|
||||||
|
@app.get('/')
|
||||||
|
def list():
|
||||||
|
student_svc_resp = requests.get(STUDENTS_MS)
|
||||||
|
students = json.loads(student_svc_resp.text)
|
||||||
|
|
||||||
|
grades_svc_resp = requests.get(GRADES_MS)
|
||||||
|
grades_list = json.loads(grades_svc_resp.text)
|
||||||
|
|
||||||
|
grades_dict = {grade_item['grade_id']:
|
||||||
|
grade_item for grade_item in grades_list}
|
||||||
|
for student in students:
|
||||||
|
student['building'] = grades_dict[student['grade']]['building']
|
||||||
|
student['teacher'] = grades_dict[student['grade']]['teacher']
|
||||||
|
|
||||||
|
return render_template('main.html', students=students)
|
||||||
|
|
||||||
|
@app.post('/')
|
||||||
|
def add():
|
||||||
|
fname = request.form['fname']
|
||||||
|
lname = request.form['lname']
|
||||||
|
grade = request.form['grade']
|
||||||
|
payload = {'name': f"{fname} {lname}", 'grade': grade}
|
||||||
|
respone = requests.post(STUDENTS_MS, data=payload)
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
@app.get('/delete/<int:id>')
|
||||||
|
def delete(id):
|
||||||
|
response = requests.delete(STUDENTS_MS+'/'+str(id))
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
@app.post('/update/<int:id>')
|
||||||
|
def update(id):
|
||||||
|
fname = request.form['fname']
|
||||||
|
lname = request.form['lname']
|
||||||
|
grade = request.form['grade']
|
||||||
|
payload = {'name' : f"{fname} {lname}", 'grade':grade}
|
||||||
|
respone = requests.put(STUDENTS_MS+'/' + str(id), data = payload)
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
@app.get('/update/<int:id>')
|
||||||
|
def load_student_for_update(id):
|
||||||
|
response = requests.get(STUDENTS_MS+'/'+str(id))
|
||||||
|
student = json.loads(response.text)
|
||||||
|
fname = student['name'].split()[0]
|
||||||
|
lname = student['name'].split()[1]
|
||||||
|
return render_template('update.html', fname=fname, lname=lname, student= student)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True, host='0.0.0.0', port=80)
|
||||||
Reference in New Issue
Block a user