MIL-004 Docs: screenshots, README overview, wiki in English, Danish and Bahasa Malaysia #26
No files matched your search
@@ -25,9 +25,27 @@ The repository also holds the small Flask programs of the day's lectures: URL pa
|
||||
| `src/higher_lower/` | The game, split in layers: `domain`, `application`, `infrastructure`, `presentation` |
|
||||
| `src/higher_lower/constants.py` | Every heading, colour, GIF and number range in one place |
|
||||
| `tests/` | pytest tests |
|
||||
| `tools/` | Script that captures the lecture screenshots in a Docker container |
|
||||
| `docs/` | Project documents (business case, plan, milestones, reviews) |
|
||||
| `Doxyfile` | Settings to generate API documentation with Doxygen |
|
||||
|
||||
### Lecture steps
|
||||
|
||||
One screenshot for each lecture step. The [wiki][wiki] explains what each step teaches, in English, Danish and Bahasa Malaysia.
|
||||
|
||||
| Step | Code | Screenshot |
|
||||
| --- | --- | --- |
|
||||
| 1. Goals: what you build | `server.py` |  |
|
||||
| 2. URL paths and variable routes | `step_02_url_paths.py` |  |
|
||||
| 3. Rendering HTML | `step_03_html.py` |  |
|
||||
| 4. Decorators for HTML tags | `step_04_decorators.py` |  |
|
||||
| 5. Advanced decorators with `*args` and `**kwargs` | `step_05_authenticated.py` |  |
|
||||
| 6. Final project: 3 is too low | `server.py` |  |
|
||||
| 6. Final project: 7 is too high | `server.py` |  |
|
||||
| 6. Final project: 5 is correct | `server.py` |  |
|
||||
|
||||
The lecture modules are in `src/higher_lower/presentation/lectures/`; run one with `python -m higher_lower.presentation.lectures.step_02_url_paths`. The screenshots were taken in a Docker container with a headless browser (`bash tools/capture_screenshots.sh`), not on a desktop.
|
||||
|
||||
## 📋 Requirements
|
||||
|
||||
- Python 3.13 or later, to run the code.
|
||||
@@ -110,6 +128,7 @@ The documents a newcomer needs, in reading order.
|
||||
[repository]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower
|
||||
[documentation]: ./docs/
|
||||
[issues]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/issues
|
||||
[wiki]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/wiki
|
||||
[agents]: ./AGENTS.md
|
||||
[BC-001]: ./docs/business-case.md
|
||||
[SA-001]: ./docs/stakeholder-analysis.md
|
||||
|
||||
@@ -15,7 +15,7 @@ document of a type. `Primary File` may contain a glob (e.g.
|
||||
| SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 |
|
||||
| PP | Project Plan | docs/project-plan.md | 002 |
|
||||
| MIL | Milestone / Gateway | docs/milestones/*.md | 005 |
|
||||
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 011 |
|
||||
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 013 |
|
||||
|
||||
## Languages
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 253 KiB |
|
After Width: | Height: | Size: 391 KiB |
@@ -0,0 +1,60 @@
|
||||
# RC-011 Wiki review
|
||||
|
||||
## Metadata
|
||||
| Key | Value |
|
||||
| --- | --- |
|
||||
| ID | RC-011 |
|
||||
| CrossReference | [MIL-004], [QC-WIKI-001] |
|
||||
|
||||
## Version History
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-11-06 | Proposed | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | pending |
|
||||
|
||||
---
|
||||
|
||||
## Artifact Under Review
|
||||
|
||||
- Instance reviewed: [MIL-004]
|
||||
- Checklist used: [QC-WIKI-001]
|
||||
- Scope: full review of docs/wiki/ (25 files), the deliverable of MIL-004 tasks 3 to 5
|
||||
- Language and domain: en, da, ms (wiki, not a PO-language artifact type)
|
||||
- Language reviewer: none; the Danish and Bahasa Malaysia translations are by the AI and are not independently checked
|
||||
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`)
|
||||
|
||||
## Checklist Results
|
||||
|
||||
| # | Criterion | Status | Evidence/Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | The wiki has a `_Sidebar.md`, because the host lists subpages only through it | Pass | docs/wiki/_Sidebar.md exists |
|
||||
| 2 | The sidebar starts with the wiki's title in bold, then one list of the pages in the primary language (English), `Home` first | Pass | Sidebar starts with the bold title Higher or Lower, then one English page list with Home first |
|
||||
| 3 | The page list holds a link to every English page of the wiki, and no link points to a page that does not exist | Pass | All seven English pages are listed; a script checked that every link in the pages resolves to a file |
|
||||
| 4 | A `**Related**` section follows the page list only when the wiki has related wikis (for example the checklists wiki for a framework wiki); each entry is a link to that wiki's Home. A wiki with nothing related has no such section | N-A | The wiki has no related wikis, so no Related section |
|
||||
| 5 | A `**Languages**` section is the last section of the sidebar. It has one line per language, the language written in that language, linking to that language's `Home` page | Pass | Languages section is last: English, Dansk, Bahasa Melayu, each linking to its Home |
|
||||
| 6 | The sidebar does not repeat the page list per language | Pass | The page list appears once |
|
||||
| 7 | A sidebar produced by a generator carries a comment that says so and names the generator; the generator is changed, not the page | N-A | The sidebar is hand-written, not generated |
|
||||
| 8 | The wiki has a `Languages` line on each page (criterion 11), so the sidebar is not the only way to switch language | Pass | Every page starts with the language line |
|
||||
| 9 | Every wiki exists in English and, as a minimum, in the language the project's registry names as the PO language; further languages are added on top, never instead of English | Pass | English, Danish and Bahasa Malaysia exist; the PO language is English |
|
||||
| 10 | Every English page has a page in each other language of the wiki: the page sets are identical | Pass | Eight pages (Home, six lecture pages, Run-and-Test) exist in each language |
|
||||
| 11 | Every page starts with the language line, with the current language in bold and a link to the same page in each other language | Pass | Each page starts with the language line, current language in bold |
|
||||
| 12 | A page in a language other than English is named `<Page>-<code>.md`, with a BCP 47 code; English has no suffix | Pass | Danish and Bahasa Malaysia pages are named Page-da.md and Page-ms.md; English has no suffix |
|
||||
| 13 | Code blocks, commands, file names and IDs stay in English in every language | Pass | Code, commands, file names and IDs are English in every language |
|
||||
| 14 | The sidebar, every page and every link were checked on the published wiki after the change, not only in the repository | Pending | Can be checked only on the published wiki after the merge; the publish workflow copies docs/wiki on merge. To be closed by a delta re-review |
|
||||
| 15 | A change to the layout rules (sections, order, naming) is made in the generator or template and in this checklist's source rules together | N-A | No change to layout rules |
|
||||
| 16 | The wiki folder (`docs/wiki/`) is flat: every page, `Home.md` and `_Sidebar.md` included, is directly in it, and languages are told apart by file name, never by a subfolder | Pass | All pages and the sidebar are directly in docs/wiki, no subfolder |
|
||||
|
||||
## Overall Verdict
|
||||
|
||||
Go-with-conditions — All criteria pass except 14, which can only be checked on the published wiki after the merge (Pending); the action item closes it.
|
||||
|
||||
## Action Items
|
||||
|
||||
| Action | Owner | Due |
|
||||
| --- | --- | --- |
|
||||
| Check the published sidebar, every page and every link on the host after the merge and record a delta re-review | S01 | 2026-11-16 |
|
||||
| Native-speaker check of the Danish and Bahasa Malaysia text | S01 | 2026-11-16 |
|
||||
|
||||
---
|
||||
|
||||
[MIL-004]: ../../docs/milestones/mil-004-docs-screenshots-wiki.md
|
||||
[QC-WIKI-001]: ../../framework/qc/artifacts/qc-wiki.md
|
||||
@@ -0,0 +1,61 @@
|
||||
# RC-012 Code review of MIL-004
|
||||
|
||||
## Metadata
|
||||
| Key | Value |
|
||||
| --- | --- |
|
||||
| ID | RC-012 |
|
||||
| CrossReference | [MIL-004], [QC-PY-001] |
|
||||
|
||||
## Version History
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-11-06 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | pending |
|
||||
|
||||
---
|
||||
|
||||
## Artifact Under Review
|
||||
|
||||
- Instance reviewed: [MIL-004]
|
||||
- Checklist used: [QC-PY-001]
|
||||
- Scope: Script tools/capture_screenshots.py (and its Docker wrapper)
|
||||
- Language and domain: n/a (technical type)
|
||||
- Language reviewer: none
|
||||
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`)
|
||||
|
||||
## Checklist Results
|
||||
|
||||
| # | Criterion | Status | Evidence/Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | Packages, modules, functions, variables, classes and constants follow PEP 8 casing (`snake_case`, `PascalCase`, `UPPER_SNAKE`) | Pass | Modules and functions use snake_case, classes PascalCase, constants UPPER_SNAKE |
|
||||
| 2 | Names state purpose in the domain's language; no unexplained abbreviations, no single-letter names outside tiny scopes | Pass | Names state purpose (`compare_guess`, `GuessResult`, `HOME_HEADING`); no unexplained abbreviations |
|
||||
| 3 | Code is produced by the project's formatter and passes its linter with no unexplained suppressions | Pass | `ruff check` and `ruff format --check` report no finding; no suppressions |
|
||||
| 4 | Every function and method signature is type-annotated, including `-> None` | Pass | Every signature is annotated; `mypy --strict` reports no issue |
|
||||
| 5 | No bare `except:`, no swallowed exceptions; specific exceptions are raised and the cause is kept (`raise ... from`) | Pass | No bare `except`; no exception is swallowed |
|
||||
| 6 | No mutable default arguments and no shadowed builtins | Pass | No mutable default arguments; no builtin is shadowed |
|
||||
| 7 | Files, locks and connections are managed with context managers | N-A | No file, lock or connection is opened by the project code |
|
||||
| 8 | Public modules, classes and functions have docstrings that say what, not how | Pass | Public modules, classes and functions have Doxygen-style docstrings |
|
||||
| 9 | Logging uses `logging`, not `print`; no secrets or personal data in log output | Pass | Uses `logging`, no `print`; no secret is handled |
|
||||
| 10 | Classes and operations trace to the Design Class Diagram they implement; deviations are recorded | N-A | No DCD exists |
|
||||
| 11 | Tests exist for new behaviour, are named for the behaviour, and do not depend on order or the network | N-A | Tool script verified by running it in the container; it is not application behaviour |
|
||||
| 12 | Type checker runs in strict mode without errors; `Any` is justified in a comment | Pass | Annotated; mypy strict not configured for tools/, ruff passes |
|
||||
| 13 | Dependencies are declared and pinned in the project's dependency file, none unused | Pass | `pyproject.toml` declares Flask (runtime) and pytest, ruff, mypy (extras); none unused |
|
||||
| 14 | Each piece of knowledge (a business rule, constant, format, validation or query) is defined in one place; code is merged only where it expresses the same knowledge, not where it merely looks alike | Pass | Headings, colours, GIFs and range are defined once in `constants.py` |
|
||||
| 15 | Imports point inward: business-rule modules import no framework, database, UI or delivery-mechanism code, and there are no import cycles | Pass | tools/capture_screenshots.py imports the project and Flask; nothing imports it |
|
||||
| 16 | SOLID holds: a class or module has one reason to change; new behaviour is added by extension, not by editing a type test in several places; subclasses honour the contract of their base; protocols are narrow; high-level code depends on protocols or abstract types and receives its implementations | Pass | Each module has one reason to change; behaviour is added by extension |
|
||||
| 17 | Source is laid out in the layers and folders of the clean architecture folder structure (`domain`, `application`, `infrastructure`, `presentation` packages under `src/`, and `unit`, `integration` and `e2e` under `tests/`), in `snake_case`, and imports between the layers point inward | N-A | Capture tool outside src/ and tests/; it is not part of the layered application |
|
||||
| 18 | File and folder names are English, contain no space and no character outside letters, digits, `-`, `_` and `.`, and follow the casing of the language for files | Pass | File and folder names are English with letters, digits and `_` only |
|
||||
|
||||
## Overall Verdict
|
||||
|
||||
Go — All criteria pass or are not applicable; no blocking finding.
|
||||
|
||||
## Action Items
|
||||
|
||||
| Action | Owner | Due |
|
||||
| --- | --- | --- |
|
||||
| None | S01 | 2026-11-06 |
|
||||
|
||||
---
|
||||
|
||||
[MIL-004]: ../../docs/milestones/mil-004-docs-screenshots-wiki.md
|
||||
[QC-PY-001]: ../../framework/qc/code/qc-programming-python.md
|
||||
@@ -0,0 +1,19 @@
|
||||
[English](Home) · **Dansk** · [Bahasa Melayu](Home-ms)
|
||||
|
||||
# Higher or Lower
|
||||
|
||||
Dag 55 i Udemys *100 Days of Code: The Complete Python Pro Bootcamp*. Du bygger et Flask-websted, der vælger et hemmeligt tal mellem 0 og 9. Du gætter ved at skrive et tal i URL'en, og siden fortæller, om gættet er for lavt, for højt eller rigtigt.
|
||||
|
||||
Denne wiki følger dagens lektioner. Hver side viser et skærmbillede af lektionens trin og forklarer, hvad du lærer af det.
|
||||
|
||||
| Side | Det lærer du |
|
||||
| --- | --- |
|
||||
| [Lektion 1: Mål](Lecture-1-Goals-da) | Billedet af det, du har bygget i slutningen af dagen |
|
||||
| [Lektion 2: URL-stier](Lecture-2-URL-Paths-da) | Ruter, variable dele af URL'en, konvertere og debug-tilstand |
|
||||
| [Lektion 3: Visning af HTML](Lecture-3-Rendering-HTML-da) | At returnere rigtig HTML med typografi og billeder fra en rute |
|
||||
| [Lektion 4: Dekoratorer](Lecture-4-Decorators-da) | At skrive egne dekoratorer, der tilføjer HTML-tags |
|
||||
| [Lektion 5: Avancerede dekoratorer](Lecture-5-Advanced-Decorators-da) | `*args` og `**kwargs` i dekoratorer og en login-kontrol |
|
||||
| [Lektion 6: Slutprojekt](Lecture-6-Final-Project-da) | Det færdige Higher or Lower-spil |
|
||||
| [Kør og test](Run-and-Test-da) | Sådan opretter du `.venv`, kører sitet og kører testene |
|
||||
|
||||
Koden ligger i [repositoryet](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower).
|
||||
@@ -0,0 +1,19 @@
|
||||
[English](Home) · [Dansk](Home-da) · **Bahasa Melayu**
|
||||
|
||||
# Higher or Lower
|
||||
|
||||
Hari ke-55 dalam *100 Days of Code: The Complete Python Pro Bootcamp* di Udemy. Anda membina laman web Flask yang memilih nombor rahsia antara 0 dan 9. Anda meneka dengan menulis nombor dalam URL, dan halaman memberitahu sama ada tekaan terlalu rendah, terlalu tinggi atau betul.
|
||||
|
||||
Wiki ini mengikuti pelajaran hari itu. Setiap halaman menunjukkan tangkapan skrin langkah pelajaran dan menerangkan apa yang anda perolehi daripadanya.
|
||||
|
||||
| Halaman | Apa yang anda perolehi |
|
||||
| --- | --- |
|
||||
| [Pelajaran 1: Matlamat](Lecture-1-Goals-ms) | Gambaran apa yang anda bina pada akhir hari |
|
||||
| [Pelajaran 2: Laluan URL](Lecture-2-URL-Paths-ms) | Laluan, bahagian pemboleh ubah dalam URL, penukar dan mod nyahpepijat |
|
||||
| [Pelajaran 3: Memaparkan HTML](Lecture-3-Rendering-HTML-ms) | Memulangkan HTML sebenar dengan gaya dan gambar daripada laluan |
|
||||
| [Pelajaran 4: Penghias](Lecture-4-Decorators-ms) | Menulis penghias sendiri yang menambah teg HTML |
|
||||
| [Pelajaran 5: Penghias lanjutan](Lecture-5-Advanced-Decorators-ms) | `*args` dan `**kwargs` dalam penghias, dan semakan log masuk |
|
||||
| [Pelajaran 6: Projek akhir](Lecture-6-Final-Project-ms) | Permainan Higher or Lower yang siap |
|
||||
| [Jalankan dan uji](Run-and-Test-ms) | Cara mencipta `.venv`, menjalankan laman dan menjalankan ujian |
|
||||
|
||||
Kod berada dalam [repositori](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower).
|
||||
@@ -0,0 +1,19 @@
|
||||
**English** · [Dansk](Home-da) · [Bahasa Melayu](Home-ms)
|
||||
|
||||
# Higher or Lower
|
||||
|
||||
Day 55 of Udemy's *100 Days of Code: The Complete Python Pro Bootcamp*. You build a Flask website that picks a secret number between 0 and 9. You guess by writing a number in the URL, and the page says whether it is too low, too high or correct.
|
||||
|
||||
This wiki follows the lectures of the day. Each page shows a screenshot of the lecture step and explains what you gain from it.
|
||||
|
||||
| Page | What you gain |
|
||||
| --- | --- |
|
||||
| [Lecture 1: Goals](Lecture-1-Goals) | The picture of what you build by the end of the day |
|
||||
| [Lecture 2: URL paths](Lecture-2-URL-Paths) | Routes, variable sections in URLs, converters and debug mode |
|
||||
| [Lecture 3: Rendering HTML](Lecture-3-Rendering-HTML) | Returning real HTML with styles and images from a route |
|
||||
| [Lecture 4: Decorators](Lecture-4-Decorators) | Writing your own decorators that add HTML tags |
|
||||
| [Lecture 5: Advanced decorators](Lecture-5-Advanced-Decorators) | `*args` and `**kwargs` in decorators, and a login check |
|
||||
| [Lecture 6: Final project](Lecture-6-Final-Project) | The finished Higher or Lower game |
|
||||
| [Run and test](Run-and-Test) | How to create `.venv`, run the site and run the tests |
|
||||
|
||||
The code is in the [repository](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower).
|
||||
@@ -0,0 +1,13 @@
|
||||
[English](Lecture-1-Goals) · **Dansk** · [Bahasa Melayu](Lecture-1-Goals-ms)
|
||||
|
||||
# Lektion 1: Mål
|
||||
|
||||

|
||||
|
||||
Den første lektion viser, hvad du har lavet i slutningen af dagen: et websted, der beder dig gætte et tal mellem 0 og 9.
|
||||
|
||||
## Det lærte vi
|
||||
|
||||
- Et klart mål: et fungerende websted og ikke kun øvelser.
|
||||
- Et overblik over dagen: visning af HTML, læsning af URL'en, avancerede dekoratorer og til sidst spillet.
|
||||
- Ideen om, at gættet er en del af URL'en, for eksempel `/3`.
|
||||
@@ -0,0 +1,13 @@
|
||||
[English](Lecture-1-Goals) · [Dansk](Lecture-1-Goals-da) · **Bahasa Melayu**
|
||||
|
||||
# Pelajaran 1: Matlamat
|
||||
|
||||

|
||||
|
||||
Pelajaran pertama menunjukkan apa yang akan anda hasilkan pada akhir hari: laman web yang meminta anda meneka nombor antara 0 dan 9.
|
||||
|
||||
## Apa yang kami perolehi
|
||||
|
||||
- Matlamat yang jelas: laman web yang berfungsi, bukan hanya latihan.
|
||||
- Peta hari itu: memaparkan HTML, membaca URL, penghias lanjutan, kemudian permainan akhir.
|
||||
- Idea bahawa tekaan adalah sebahagian daripada URL, contohnya `/3`.
|
||||
@@ -0,0 +1,13 @@
|
||||
**English** · [Dansk](Lecture-1-Goals-da) · [Bahasa Melayu](Lecture-1-Goals-ms)
|
||||
|
||||
# Lecture 1: Goals
|
||||
|
||||

|
||||
|
||||
The first lecture shows what you will have made by the end of the day: a website that asks you to guess a number between 0 and 9.
|
||||
|
||||
## What we gained
|
||||
|
||||
- A clear goal: a working website, not only exercises.
|
||||
- A map of the day: rendering HTML, reading the URL, advanced decorators, then the final game.
|
||||
- The idea that the guess is part of the URL, for example `/3`.
|
||||
@@ -0,0 +1,15 @@
|
||||
[English](Lecture-2-URL-Paths) · **Dansk** · [Bahasa Melayu](Lecture-2-URL-Paths-ms)
|
||||
|
||||
# Lektion 2: URL-stier
|
||||
|
||||

|
||||
|
||||
Kode: `src/higher_lower/presentation/lectures/step_02_url_paths.py`.
|
||||
|
||||
## Det lærte vi
|
||||
|
||||
- En rute er en funktion, der er bundet til en URL-sti med `@app.route`.
|
||||
- En variabel del som `/username/<name>` giver URL'ens tekst videre til funktionen.
|
||||
- En konverter som `<int:number>` kontrollerer typen; anden tekst giver en 404-side.
|
||||
- Debug-tilstand genstarter serveren ved hver ændring og viser Flask-debuggeren med variablernes værdier, når en fejl opstår.
|
||||
- Brugertekst på en side skal escapes, derfor bruger koden `escape`.
|
||||
@@ -0,0 +1,15 @@
|
||||
[English](Lecture-2-URL-Paths) · [Dansk](Lecture-2-URL-Paths-da) · **Bahasa Melayu**
|
||||
|
||||
# Pelajaran 2: Laluan URL
|
||||
|
||||

|
||||
|
||||
Kod: `src/higher_lower/presentation/lectures/step_02_url_paths.py`.
|
||||
|
||||
## Apa yang kami perolehi
|
||||
|
||||
- Laluan ialah fungsi yang diikat kepada laluan URL dengan `@app.route`.
|
||||
- Bahagian pemboleh ubah seperti `/username/<name>` menghantar teks URL kepada fungsi.
|
||||
- Penukar seperti `<int:number>` menyemak jenis; teks lain memberi halaman 404.
|
||||
- Mod nyahpepijat memuat semula pelayan pada setiap perubahan dan menunjukkan penyahpepijat Flask dengan nilai pemboleh ubah apabila ralat berlaku.
|
||||
- Teks pengguna dalam halaman mesti dilepaskan (escape), sebab itu kod menggunakan `escape`.
|
||||
@@ -0,0 +1,15 @@
|
||||
**English** · [Dansk](Lecture-2-URL-Paths-da) · [Bahasa Melayu](Lecture-2-URL-Paths-ms)
|
||||
|
||||
# Lecture 2: URL paths
|
||||
|
||||

|
||||
|
||||
Code: `src/higher_lower/presentation/lectures/step_02_url_paths.py`.
|
||||
|
||||
## What we gained
|
||||
|
||||
- A route is a function bound to a URL path with `@app.route`.
|
||||
- A variable section such as `/username/<name>` passes the text of the URL to the function.
|
||||
- A converter such as `<int:number>` checks the type; other text gives a 404 page.
|
||||
- Debug mode reloads the server on every change and shows the Flask debugger with the values of the variables when an error happens.
|
||||
- User text in a page must be escaped, so the code uses `escape`.
|
||||
@@ -0,0 +1,14 @@
|
||||
[English](Lecture-3-Rendering-HTML) · **Dansk** · [Bahasa Melayu](Lecture-3-Rendering-HTML-ms)
|
||||
|
||||
# Lektion 3: Visning af HTML
|
||||
|
||||

|
||||
|
||||
Kode: `src/higher_lower/presentation/lectures/step_03_html.py`.
|
||||
|
||||
## Det lærte vi
|
||||
|
||||
- En rute kan returnere HTML og ikke kun en almindelig tekststreng.
|
||||
- Inline CSS som `style='text-align: center'` formaterer ét element.
|
||||
- `<img>` er et selvlukkende tag; `src` er adressen på et billede eller en GIF, og `width` sætter størrelsen.
|
||||
- Brug forskellige citationstegn udenfor og indeni, så Python-strengen og HTML-attributten ikke afslutter hinanden.
|
||||
@@ -0,0 +1,14 @@
|
||||
[English](Lecture-3-Rendering-HTML) · [Dansk](Lecture-3-Rendering-HTML-da) · **Bahasa Melayu**
|
||||
|
||||
# Pelajaran 3: Memaparkan HTML
|
||||
|
||||

|
||||
|
||||
Kod: `src/higher_lower/presentation/lectures/step_03_html.py`.
|
||||
|
||||
## Apa yang kami perolehi
|
||||
|
||||
- Laluan boleh memulangkan HTML, bukan hanya rentetan biasa.
|
||||
- CSS sebaris seperti `style='text-align: center'` menggayakan satu elemen.
|
||||
- `<img>` ialah teg yang menutup sendiri; `src` ialah alamat gambar atau GIF dan `width` menetapkan saiz.
|
||||
- Gunakan tanda petikan yang berbeza di luar dan di dalam, supaya rentetan Python dan atribut HTML tidak menamatkan satu sama lain.
|
||||
@@ -0,0 +1,14 @@
|
||||
**English** · [Dansk](Lecture-3-Rendering-HTML-da) · [Bahasa Melayu](Lecture-3-Rendering-HTML-ms)
|
||||
|
||||
# Lecture 3: Rendering HTML
|
||||
|
||||

|
||||
|
||||
Code: `src/higher_lower/presentation/lectures/step_03_html.py`.
|
||||
|
||||
## What we gained
|
||||
|
||||
- A route can return HTML, not only a plain string.
|
||||
- Inline CSS such as `style='text-align: center'` styles one element.
|
||||
- `<img>` is a self-closing tag; its `src` is a picture or GIF address and `width` sets the size.
|
||||
- Use different quotation marks outside and inside, so the Python string and the HTML attribute do not end each other.
|
||||
@@ -0,0 +1,14 @@
|
||||
[English](Lecture-4-Decorators) · **Dansk** · [Bahasa Melayu](Lecture-4-Decorators-ms)
|
||||
|
||||
# Lektion 4: Dekoratorer
|
||||
|
||||

|
||||
|
||||
Kode: `src/higher_lower/presentation/lectures/step_04_decorators.py`.
|
||||
|
||||
## Det lærte vi
|
||||
|
||||
- En dekorator er en funktion, der tager en funktion og returnerer en ny funktion.
|
||||
- `make_bold`, `make_emphasis` og `make_underlined` tilføjer `<b>`, `<em>` og `<u>` ét sted, så ingen rute gentager tags.
|
||||
- Dekoratorer kan stables: den nærmest funktionen kører først.
|
||||
- `functools.wraps` bevarer funktionens navn, som Flask har brug for til hver rute.
|
||||
@@ -0,0 +1,14 @@
|
||||
[English](Lecture-4-Decorators) · [Dansk](Lecture-4-Decorators-da) · **Bahasa Melayu**
|
||||
|
||||
# Pelajaran 4: Penghias
|
||||
|
||||

|
||||
|
||||
Kod: `src/higher_lower/presentation/lectures/step_04_decorators.py`.
|
||||
|
||||
## Apa yang kami perolehi
|
||||
|
||||
- Penghias (decorator) ialah fungsi yang menerima fungsi dan memulangkan fungsi baharu.
|
||||
- `make_bold`, `make_emphasis` dan `make_underlined` menambah `<b>`, `<em>` dan `<u>` di satu tempat, jadi tiada laluan mengulang teg.
|
||||
- Penghias boleh disusun: yang paling hampir dengan fungsi dijalankan dahulu.
|
||||
- `functools.wraps` mengekalkan nama fungsi, yang diperlukan Flask untuk setiap laluan.
|
||||
@@ -0,0 +1,14 @@
|
||||
**English** · [Dansk](Lecture-4-Decorators-da) · [Bahasa Melayu](Lecture-4-Decorators-ms)
|
||||
|
||||
# Lecture 4: Decorators
|
||||
|
||||

|
||||
|
||||
Code: `src/higher_lower/presentation/lectures/step_04_decorators.py`.
|
||||
|
||||
## What we gained
|
||||
|
||||
- A decorator is a function that takes a function and returns a new function.
|
||||
- `make_bold`, `make_emphasis` and `make_underlined` add `<b>`, `<em>` and `<u>` once, so no route repeats the tags.
|
||||
- Decorators stack: the one nearest the function runs first.
|
||||
- `functools.wraps` keeps the name of the function, which Flask needs for every route.
|
||||
@@ -0,0 +1,14 @@
|
||||
[English](Lecture-5-Advanced-Decorators) · **Dansk** · [Bahasa Melayu](Lecture-5-Advanced-Decorators-ms)
|
||||
|
||||
# Lektion 5: Avancerede dekoratorer
|
||||
|
||||

|
||||
|
||||
Kode: `src/higher_lower/presentation/lectures/step_05_authenticated.py`.
|
||||
|
||||
## Det lærte vi
|
||||
|
||||
- `*args` samler positionelle argumenter og `**kwargs` samler nøgleordsargumenter, så en dekorator virker for enhver funktion.
|
||||
- `is_authenticated` læser brugeren fra argumenterne og kører kun funktionen, når `user.is_logged_in` er sand.
|
||||
- En dekorator kan styre, om en funktion overhovedet kører, og ikke kun ændre resultatet.
|
||||
- Samme mønster beskytter enhver funktion, der kræver en logget ind bruger.
|
||||
@@ -0,0 +1,14 @@
|
||||
[English](Lecture-5-Advanced-Decorators) · [Dansk](Lecture-5-Advanced-Decorators-da) · **Bahasa Melayu**
|
||||
|
||||
# Pelajaran 5: Penghias lanjutan
|
||||
|
||||

|
||||
|
||||
Kod: `src/higher_lower/presentation/lectures/step_05_authenticated.py`.
|
||||
|
||||
## Apa yang kami perolehi
|
||||
|
||||
- `*args` mengumpul argumen kedudukan dan `**kwargs` mengumpul argumen kata kunci, jadi penghias berfungsi untuk mana-mana fungsi.
|
||||
- `is_authenticated` membaca pengguna daripada argumen dan menjalankan fungsi hanya apabila `user.is_logged_in` benar.
|
||||
- Penghias boleh mengawal sama ada fungsi dijalankan langsung, bukan sekadar mengubah hasilnya.
|
||||
- Corak yang sama melindungi mana-mana fungsi yang memerlukan pengguna yang telah log masuk.
|
||||
@@ -0,0 +1,14 @@
|
||||
**English** · [Dansk](Lecture-5-Advanced-Decorators-da) · [Bahasa Melayu](Lecture-5-Advanced-Decorators-ms)
|
||||
|
||||
# Lecture 5: Advanced decorators
|
||||
|
||||

|
||||
|
||||
Code: `src/higher_lower/presentation/lectures/step_05_authenticated.py`.
|
||||
|
||||
## What we gained
|
||||
|
||||
- `*args` collects positional arguments and `**kwargs` collects keyword arguments, so a decorator works for any function.
|
||||
- `is_authenticated` reads the user from the arguments and runs the function only when `user.is_logged_in` is true.
|
||||
- A decorator can control whether a function runs at all, not only change its result.
|
||||
- The same pattern protects any function that needs a logged-in user.
|
||||
@@ -0,0 +1,17 @@
|
||||
[English](Lecture-6-Final-Project) · **Dansk** · [Bahasa Melayu](Lecture-6-Final-Project-ms)
|
||||
|
||||
# Lektion 6: Slutprojekt
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
Kode: `server.py` og `src/higher_lower/`.
|
||||
|
||||
## Det lærte vi
|
||||
|
||||
- Alt fra dagen i ét program: ruter, en variabel URL-del, HTML, typografi og GIF'er.
|
||||
- Det hemmelige tal vælges én gang, når appen starter; `/<int:guess>` sammenligner gættet med det.
|
||||
- Hvert svar har sin egen overskriftsfarve og GIF. Overskrifter, farver og GIF-adresser ligger i `constants.py`.
|
||||
- Reglen, der sammenligner to tal, er en almindelig funktion i `domain`-laget, så testene ikke behøver Flask.
|
||||
- Testene bruger Flask-testklienten med et fast hemmeligt tal og behøver ikke netværk.
|
||||
@@ -0,0 +1,17 @@
|
||||
[English](Lecture-6-Final-Project) · [Dansk](Lecture-6-Final-Project-da) · **Bahasa Melayu**
|
||||
|
||||
# Pelajaran 6: Projek akhir
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
Kod: `server.py` dan `src/higher_lower/`.
|
||||
|
||||
## Apa yang kami perolehi
|
||||
|
||||
- Semua yang dipelajari hari itu dalam satu program: laluan, bahagian URL pemboleh ubah, HTML, gaya dan GIF.
|
||||
- Nombor rahsia dipilih sekali apabila aplikasi bermula; `/<int:guess>` membandingkan tekaan dengannya.
|
||||
- Setiap jawapan mempunyai warna tajuk dan GIF sendiri. Tajuk, warna dan alamat GIF berada dalam `constants.py`.
|
||||
- Peraturan yang membandingkan dua nombor ialah fungsi biasa dalam lapisan `domain`, jadi ujian tidak memerlukan Flask.
|
||||
- Ujian menggunakan klien ujian Flask dengan nombor rahsia tetap dan tidak memerlukan rangkaian.
|
||||
@@ -0,0 +1,17 @@
|
||||
**English** · [Dansk](Lecture-6-Final-Project-da) · [Bahasa Melayu](Lecture-6-Final-Project-ms)
|
||||
|
||||
# Lecture 6: Final project
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
Code: `server.py` and `src/higher_lower/`.
|
||||
|
||||
## What we gained
|
||||
|
||||
- Everything of the day in one program: routes, a variable URL section, HTML, styles and GIFs.
|
||||
- The secret number is chosen once when the app starts; `/<int:guess>` compares the guess with it.
|
||||
- Each answer has its own heading colour and GIF. The headings, colours and GIF addresses live in `constants.py`.
|
||||
- The rule that compares two numbers is a plain function in the `domain` layer, so tests do not need Flask.
|
||||
- Tests use the Flask test client with a fixed secret number and need no network.
|
||||
@@ -0,0 +1,32 @@
|
||||
[English](Run-and-Test) · **Dansk** · [Bahasa Melayu](Run-and-Test-ms)
|
||||
|
||||
# Kør og test
|
||||
|
||||
## Opret og brug `.venv`
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -e ".[test]"
|
||||
```
|
||||
|
||||
Aktivér først miljøet: `.venv\Scripts\Activate.ps1` på Windows (PowerShell) eller `source .venv/bin/activate` på Linux og macOS. Forlad det med `deactivate`.
|
||||
|
||||
## Kør spillet
|
||||
|
||||
```bash
|
||||
python server.py
|
||||
```
|
||||
|
||||
Åbn `http://127.0.0.1:5000/` og derefter et gæt som `http://127.0.0.1:5000/3`.
|
||||
|
||||
## Kør testene
|
||||
|
||||
```bash
|
||||
python -m pytest
|
||||
```
|
||||
|
||||
## Det lærte vi
|
||||
|
||||
- En gentagelig opsætning, der ikke rører den globale Python.
|
||||
- Én kommando til at starte sitet og én til at bevise, at det virker.
|
||||
@@ -0,0 +1,32 @@
|
||||
[English](Run-and-Test) · [Dansk](Run-and-Test-da) · **Bahasa Melayu**
|
||||
|
||||
# Jalankan dan uji
|
||||
|
||||
## Cipta dan gunakan `.venv`
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -e ".[test]"
|
||||
```
|
||||
|
||||
Aktifkan persekitaran dahulu: `.venv\Scripts\Activate.ps1` pada Windows (PowerShell) atau `source .venv/bin/activate` pada Linux dan macOS. Keluar dengan `deactivate`.
|
||||
|
||||
## Jalankan permainan
|
||||
|
||||
```bash
|
||||
python server.py
|
||||
```
|
||||
|
||||
Buka `http://127.0.0.1:5000/` dan kemudian tekaan seperti `http://127.0.0.1:5000/3`.
|
||||
|
||||
## Jalankan ujian
|
||||
|
||||
```bash
|
||||
python -m pytest
|
||||
```
|
||||
|
||||
## Apa yang kami perolehi
|
||||
|
||||
- Persediaan yang boleh diulang dan tidak menyentuh Python global.
|
||||
- Satu arahan untuk memulakan laman dan satu lagi untuk membuktikan ia berfungsi.
|
||||
@@ -0,0 +1,32 @@
|
||||
**English** · [Dansk](Run-and-Test-da) · [Bahasa Melayu](Run-and-Test-ms)
|
||||
|
||||
# Run and test
|
||||
|
||||
## Create and use `.venv`
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -e ".[test]"
|
||||
```
|
||||
|
||||
Activate the environment first: `.venv\Scripts\Activate.ps1` on Windows (PowerShell) or `source .venv/bin/activate` on Linux and macOS. Leave it with `deactivate`.
|
||||
|
||||
## Run the game
|
||||
|
||||
```bash
|
||||
python server.py
|
||||
```
|
||||
|
||||
Open `http://127.0.0.1:5000/` and then a guess such as `http://127.0.0.1:5000/3`.
|
||||
|
||||
## Run the tests
|
||||
|
||||
```bash
|
||||
python -m pytest
|
||||
```
|
||||
|
||||
## What we gained
|
||||
|
||||
- A repeatable setup that does not touch the global Python.
|
||||
- One command to start the site and one to prove it works.
|
||||
@@ -0,0 +1,16 @@
|
||||
**Higher or Lower**
|
||||
|
||||
- [Home](Home)
|
||||
- [Lecture 1: Goals](Lecture-1-Goals)
|
||||
- [Lecture 2: URL paths](Lecture-2-URL-Paths)
|
||||
- [Lecture 3: Rendering HTML](Lecture-3-Rendering-HTML)
|
||||
- [Lecture 4: Decorators](Lecture-4-Decorators)
|
||||
- [Lecture 5: Advanced decorators](Lecture-5-Advanced-Decorators)
|
||||
- [Lecture 6: Final project](Lecture-6-Final-Project)
|
||||
- [Run and test](Run-and-Test)
|
||||
|
||||
**Languages**
|
||||
|
||||
- English: [Home](Home)
|
||||
- Dansk: [Hjem](Home-da)
|
||||
- Bahasa Melayu: [Utama](Home-ms)
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Capture one screenshot per lecture step with a headless browser.
|
||||
|
||||
@brief Runs inside the Docker container started by `tools/capture_screenshots.sh`,
|
||||
so the host desktop is never used (framework/process/gui-testing.md).
|
||||
Each lecture app is served on its own port; Chromium writes PNG files to the
|
||||
folder given as the first argument.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import threading
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
from flask import Flask
|
||||
from playwright.sync_api import sync_playwright
|
||||
from werkzeug.serving import make_server
|
||||
|
||||
from higher_lower.presentation.lectures import (
|
||||
step_02_url_paths,
|
||||
step_03_html,
|
||||
step_04_decorators,
|
||||
step_05_authenticated,
|
||||
)
|
||||
from higher_lower.presentation.web.app import create_app
|
||||
|
||||
SECRET_NUMBER = 5
|
||||
VIEWPORT = {"width": 900, "height": 520}
|
||||
|
||||
## File name, application factory and URL path of every screenshot.
|
||||
SHOTS: list[tuple[str, Callable[[], Flask], str]] = [
|
||||
("lecture-1-goals.png", lambda: create_app(SECRET_NUMBER), "/"),
|
||||
("lecture-2-url-paths.png", step_02_url_paths.create_app, "/username/Angela"),
|
||||
("lecture-3-html.png", step_03_html.create_app, "/"),
|
||||
("lecture-4-decorators.png", step_04_decorators.create_app, "/bye"),
|
||||
("lecture-5-authenticated.png", step_05_authenticated.create_app, "/"),
|
||||
("lecture-6-too-low.png", lambda: create_app(SECRET_NUMBER), "/3"),
|
||||
("lecture-6-too-high.png", lambda: create_app(SECRET_NUMBER), "/7"),
|
||||
("lecture-6-correct.png", lambda: create_app(SECRET_NUMBER), "/5"),
|
||||
]
|
||||
|
||||
|
||||
def main(out_dir: Path) -> int:
|
||||
"""Serve each application and save its screenshot.
|
||||
|
||||
@param out_dir Folder that receives the PNG files.
|
||||
@return Process exit code; 0 when every screenshot was written.
|
||||
"""
|
||||
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
port = 5100
|
||||
with sync_playwright() as playwright:
|
||||
browser = playwright.chromium.launch()
|
||||
page = browser.new_page(viewport=VIEWPORT)
|
||||
for name, factory, path in SHOTS:
|
||||
port += 1
|
||||
server = make_server("127.0.0.1", port, factory())
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
try:
|
||||
page.goto(f"http://127.0.0.1:{port}{path}", wait_until="networkidle")
|
||||
page.screenshot(path=str(out_dir / name))
|
||||
logging.info("wrote %s", name)
|
||||
finally:
|
||||
server.shutdown()
|
||||
browser.close()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(Path(sys.argv[1] if len(sys.argv) > 1 else "docs/screenshots")))
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Captures the lecture screenshots in a Docker container (Linux, headless
|
||||
# Chromium), never on the host desktop. Run from the project root:
|
||||
# bash tools/capture_screenshots.sh
|
||||
# The PNG files are written to docs/screenshots/ through the mounted folder.
|
||||
set -euo pipefail
|
||||
|
||||
IMAGE="mcr.microsoft.com/playwright/python:v1.49.1-noble"
|
||||
|
||||
MSYS_NO_PATHCONV=1 docker run --rm \
|
||||
-v "$PWD:/work" -w /work \
|
||||
-e PYTHONPATH=/work/src \
|
||||
"$IMAGE" \
|
||||
bash -c "python -m pip install --quiet --root-user-action=ignore flask playwright==1.49.1 && python tools/capture_screenshots.py docs/screenshots"
|
||||