Chapter 37 Quiz: Building Simple Business Applications with Flask
Instructions: Select the best answer for each multiple-choice question. For short-answer questions, write 2–4 sentences. Answer key follows all questions.
Multiple Choice (Questions 1–12)
1. Flask is best described as:
A) A full-featured web framework that includes built-in user authentication and an admin interface B) A microframework that provides routing and templates, leaving other choices to the developer C) A web server designed to replace Nginx or Apache in production environments D) A Python library for building command-line tools that can optionally serve web pages
2. Which of the following correctly creates a Flask route for a URL like /client/42/report?
A) @app.route("/client/<client_id>/report")
B) @app.route("/client/*/report")
C) @app.route("/client/<int:client_id>/report")
D) Both A and C are correct, but C provides automatic type conversion
3. In a Jinja2 template, what is the difference between {{ }} and {% %}?
A) {{ }} is for HTML; {% %} is for Python
B) {{ }} outputs a value; {% %} contains control logic (loops, conditionals, etc.)
C) {{ }} is evaluated at runtime; {% %} is evaluated at compile time
D) {{ }} is for variables passed from Flask; {% %} is for variables defined inside the template
4. A user fills out an expense form and clicks Submit. The server processes the submission and calls return redirect(url_for("expense_success")). The user immediately presses the browser's Back button, then presses the browser's Refresh button while on the expense form page. What happens?
A) The form is re-submitted automatically, creating a duplicate expense record B) The browser shows a dialog asking whether to re-submit the form C) The browser reloads the form page with a GET request (no re-submission occurs) D) The server returns a 405 Method Not Allowed error
5. Which statement about Flask's request object is TRUE?
A) request.form["field_name"] returns None if the field does not exist
B) request.args contains data submitted via POST forms
C) request.form.get("field_name") returns None if the field does not exist
D) request.method always returns "GET" unless the route explicitly lists POST
6. You want to add CSS styling to your Flask application. Which approach is correct?
A) Write the CSS directly in the Python functions that generate HTML strings
B) Create a css/ folder at the root of your project and reference files directly
C) Place CSS files in the static/ folder and reference them with url_for('static', filename='css/style.css')
D) Flask does not support CSS; use inline styles in your templates only
7. What is template inheritance in Jinja2?
A) The ability to import a Python class from another template file
B) A pattern where child templates override specific blocks defined in a parent base.html template
C) The automatic sharing of variables between multiple templates in the same request
D) Flask's built-in system for reusing form field definitions across pages
8. You run your Flask app with app.run(debug=True) and deploy it to a company server where colleagues can access it. Why is this a security risk?
A) debug=True disables HTTPS, exposing data in transit B) The interactive debugger can execute arbitrary Python code in the browser, giving access to any attacker who can reach the URL C) debug=True logs all user passwords in plain text to the console D) Flask in debug mode does not serve static files, breaking the UI
9. In the Acme Corp application, SECRET_KEY is loaded from an environment variable. What would happen if SECRET_KEY were hardcoded in app.py and pushed to a public GitHub repository?
A) Nothing — SECRET_KEY only controls the application's visual theme
B) An attacker could forge session cookies, bypassing the login check entirely
C) The application would refuse to start, since Flask requires environment variable configuration
D) Other developers could clone the repo and see the application but not the data
10. Flask's g object is used in the database connection pattern (get_db()). What does g represent?
A) A global variable that persists for the lifetime of the application B) A per-request store that is reset between HTTP requests C) A connection pool that manages multiple database connections simultaneously D) A cache that stores query results for frequently accessed data
11. When building an internal business tool with Flask, which authentication approach is described in this chapter as appropriate for intranet tools but NOT suitable for public-facing applications?
A) OAuth with Google or Microsoft Single Sign-On
B) Flask-Login with hashed passwords stored in a database
C) A shared password stored in an environment variable, checked against session["authenticated"]
D) JSON Web Tokens (JWT) in the Authorization header
12. What is the difference between Flask's development server and Gunicorn?
A) Gunicorn requires a commercial license; Flask's server is free B) Flask's server handles one request at a time; Gunicorn spawns multiple worker processes for concurrent requests and is suitable for production C) Gunicorn only runs on macOS; Flask's server is cross-platform D) Flask's server supports HTTPS natively; Gunicorn requires a separate TLS termination layer
Short Answer (Questions 13–17)
13. Explain why url_for('dashboard') is preferred over the hardcoded string "/dashboard" in Flask templates and Python code. Describe one concrete scenario where using url_for() prevents a bug.
14. What is the methods=["GET", "POST"] parameter in @app.route() doing, and what happens to the unspecified methods (like PUT or DELETE) when someone sends them to that route?
15. The chapter states that Flask "has no concept of user accounts, roles, or permissions" out of the box. Given this limitation, why might you still choose Flask over Django for an internal business tool that only three people will use?
16. Describe the role of python-dotenv in a Flask application. Why should the .env file always be in .gitignore? Give one example of what could go wrong if it were committed to a public repository.
17. In the Jinja2 template for the dashboard, the chapter uses:
${{ "{:,.0f}".format(metrics.total_revenue) }}
Why does this work in Jinja2, even though {:,.0f} looks like Python's str.format() method? What is {:,.0f} actually formatting, and what would the output look like for 1245780.5?
Answer Key
Multiple Choice
1. B — Flask is a microframework. Django is the "full-featured" framework described in A. Flask is not a web server (C). It does not build CLI tools (D). The defining Flask characteristic is the "micro" philosophy: minimum built-in assumptions, maximum flexibility.
2. D — Both A and C create valid routes, but C provides automatic type conversion. If you visit /client/42/report, both routes match. However, <int:client_id> automatically converts "42" to the integer 42, and returns a 404 automatically if the segment is not a valid integer (e.g., /client/abc/report). Without the int: converter, the value arrives as a string.
3. B — {{ variable }} outputs a value into the rendered HTML. {% statement %} contains Jinja2 logic (if/elif/else, for loops, extends, block, macro definitions) that controls template processing but does not itself produce output.
4. B — The PRG (Post/Redirect/Get) pattern redirects after the POST to a new GET request. When the user presses Back, they return to the form page URL. When they press Refresh, the browser re-issues the GET request for the success page (the URL they were redirected to) — not a re-POST. However, if the user presses Back to the form page and then Refresh, the browser will typically show a dialog asking whether to resubmit, because the form page itself has a POST in its history. The correct behavior after Back-then-Refresh on the form page is B.
5. C — request.form.get("field_name") safely returns None if the field does not exist. request.form["field_name"] raises a KeyError (A is wrong). request.args contains URL query parameters (GET parameters), not POST form data (B is wrong). request.method reflects the actual HTTP method of the request (D is wrong — Flask does not override this).
6. C — Flask serves static files from the static/ directory. Reference them in templates with url_for('static', filename='...'). Option A (CSS in Python strings) works technically but is unmaintainable. Option B would create a path that Flask does not know to serve. Option D is false.
7. B — Template inheritance allows a base.html file to define shared layout with {% block name %}{% endblock %} placeholders. Child templates use {% extends "base.html" %} and override specific blocks with their own content, avoiding duplication of common structure.
8. B — Flask's interactive debugger allows execution of arbitrary Python code in the browser when an exception occurs. An attacker who can reach the URL and trigger an error can run any Python code on the server — reading files, accessing databases, or compromising the host system. This is a documented, intentional design for developer convenience, explicitly not for production use.
9. B — SECRET_KEY is used to sign session cookies cryptographically. If an attacker knows the key, they can craft a cookie that Flask will trust as a legitimate signed session, bypassing any session-based authentication check (session.get("authenticated")). This is a critical security vulnerability.
10. B — g is Flask's per-request application context store. It is created fresh for each HTTP request and destroyed when the request ends. The @app.teardown_appcontext decorator closes the database connection when g is cleaned up. This ensures one connection per request, not one per application lifecycle.
11. C — The chapter explicitly describes the shared-password session approach as appropriate for internal intranet tools with controlled network access, while noting it is not suitable for public-facing applications, applications with PII, or those requiring audit logging. Flask-Login (B) and OAuth (A) are presented as the alternatives for production-grade auth.
12. B — Flask's built-in development server is single-threaded by default — it handles one request at a time. Gunicorn is a multi-process WSGI server that spawns multiple worker processes, each handling one request at a time, providing true concurrency. Gunicorn is the standard production WSGI server for Flask on Linux/macOS. (Waitress is the Windows alternative.) C, A, and D are all false.
Short Answer — Model Responses
13. url_for('dashboard') generates the URL for the route function named dashboard dynamically. If you later change the route from /dashboard to /sales/dashboard, every url_for('dashboard') call in your templates and Python code updates automatically. With a hardcoded string, you would need to find and update every occurrence manually.
Concrete scenario: You rename the expense route from /expenses to /expense-submission because your IT department has a URL naming standard. With url_for('expense_form') throughout, the change requires editing only one @app.route() decorator. With hardcoded strings, you would need to find every link, redirect, and reference — almost certainly missing one and creating a broken link.
14. methods=["GET", "POST"] tells Flask that the route should respond to both GET and POST HTTP requests. Inside the route function, request.method lets you differentiate between them. If a client sends a request using any other method (PUT, DELETE, PATCH) to this URL, Flask returns a 405 Method Not Allowed response automatically, without calling your route function.
15. Django's strengths (built-in admin, user models, ORM, migration system) are also its complexity costs. For a three-person internal tool with simple data and no public exposure, the overhead of learning Django's project structure, configuring settings, managing migrations, and working within its conventions exceeds the benefit. Flask lets you build a functional tool in a single Python file, deploy it in an afternoon, and modify it without understanding a framework's extensive conventions. The right tool is proportional to the problem. When the team grows, the requirements grow more complex, or security requirements increase, reassessing is appropriate.
16. python-dotenv reads a .env file and loads its contents into the process's environment variables. This allows you to store sensitive configuration (passwords, API keys, secret keys) outside of source code. The .env file belongs in .gitignore because source code often ends up in version control systems, shared repositories, or CI/CD pipelines where unintended parties could access it. Example of what could go wrong: If the .env contains DASHBOARD_PASSWORD=AcmeSales2024! and the repository is public on GitHub, anyone who clones the repository can immediately access the dashboard. If the file contains cloud API keys, attackers can run up significant charges or exfiltrate data before you notice.
17. In Jinja2's {{ }} blocks, you can call any Python expression, including string methods. "{:,.0f}" is a Python format string (a regular Python string, not Jinja2 syntax). Calling .format(1245780.5) on it invokes Python's str.format() method. The format spec :,.0f means: comma as thousands separator, no decimal places, treat as a floating-point number. For 1245780.5, the output would be 1,245,781 (rounded to zero decimal places). The full template expression ${{ "{:,.0f}".format(metrics.total_revenue) }}` would produce `$1,245,781 in the rendered HTML.