MIL-004 Docs: screenshots, README overview, wiki in English, Danish and Bahasa Malaysia #26

Merged
Tirsvad merged 5 commits from mil-004-docs-screenshots-wiki into main 2026-11-06 06:07:42 +01:00
39 changed files with 656 additions and 1 deletions
Showing only changes of commit 1887fc347f - Show all commits

No files matched your search

+19
View File
@@ -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` | ![Start page of the game](docs/screenshots/lecture-1-goals.png) |
| 2. URL paths and variable routes | `step_02_url_paths.py` | ![Route /username/Angela](docs/screenshots/lecture-2-url-paths.png) |
| 3. Rendering HTML | `step_03_html.py` | ![Heading, paragraph and images](docs/screenshots/lecture-3-html.png) |
| 4. Decorators for HTML tags | `step_04_decorators.py` | ![Route /bye in bold, emphasis and underline](docs/screenshots/lecture-4-decorators.png) |
| 5. Advanced decorators with `*args` and `**kwargs` | `step_05_authenticated.py` | ![Logged in and logged out user](docs/screenshots/lecture-5-authenticated.png) |
| 6. Final project: 3 is too low | `server.py` | ![3 is too low](docs/screenshots/lecture-6-too-low.png) |
| 6. Final project: 7 is too high | `server.py` | ![7 is too high](docs/screenshots/lecture-6-too-high.png) |
| 6. Final project: 5 is correct | `server.py` | ![5 is correct](docs/screenshots/lecture-6-correct.png) |
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
+1 -1
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

+60
View File
@@ -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
+61
View File
@@ -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
+19
View File
@@ -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).
+19
View File
@@ -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).
+19
View File
@@ -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).
+13
View File
@@ -0,0 +1,13 @@
[English](Lecture-1-Goals) · **Dansk** · [Bahasa Melayu](Lecture-1-Goals-ms)
# Lektion 1: Mål
![Startsiden i det færdige spil](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-1-goals.png)
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`.
+13
View File
@@ -0,0 +1,13 @@
[English](Lecture-1-Goals) · [Dansk](Lecture-1-Goals-da) · **Bahasa Melayu**
# Pelajaran 1: Matlamat
![Halaman permulaan permainan yang siap](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-1-goals.png)
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`.
+13
View File
@@ -0,0 +1,13 @@
**English** · [Dansk](Lecture-1-Goals-da) · [Bahasa Melayu](Lecture-1-Goals-ms)
# Lecture 1: Goals
![The start page of the finished game](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-1-goals.png)
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`.
+15
View File
@@ -0,0 +1,15 @@
[English](Lecture-2-URL-Paths) · **Dansk** · [Bahasa Melayu](Lecture-2-URL-Paths-ms)
# Lektion 2: URL-stier
![Ruten /username/Angela hilser på brugeren](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-2-url-paths.png)
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`.
+15
View File
@@ -0,0 +1,15 @@
[English](Lecture-2-URL-Paths) · [Dansk](Lecture-2-URL-Paths-da) · **Bahasa Melayu**
# Pelajaran 2: Laluan URL
![Laluan /username/Angela menyambut pengguna](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-2-url-paths.png)
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`.
+15
View File
@@ -0,0 +1,15 @@
**English** · [Dansk](Lecture-2-URL-Paths-da) · [Bahasa Melayu](Lecture-2-URL-Paths-ms)
# Lecture 2: URL paths
![The route /username/Angela greets the user](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-2-url-paths.png)
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`.
+14
View File
@@ -0,0 +1,14 @@
[English](Lecture-3-Rendering-HTML) · **Dansk** · [Bahasa Melayu](Lecture-3-Rendering-HTML-ms)
# Lektion 3: Visning af HTML
![En centreret overskrift, et afsnit og to billeder](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-3-html.png)
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.
+14
View File
@@ -0,0 +1,14 @@
[English](Lecture-3-Rendering-HTML) · [Dansk](Lecture-3-Rendering-HTML-da) · **Bahasa Melayu**
# Pelajaran 3: Memaparkan HTML
![Tajuk di tengah, satu perenggan dan dua gambar](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-3-html.png)
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.
+14
View File
@@ -0,0 +1,14 @@
**English** · [Dansk](Lecture-3-Rendering-HTML-da) · [Bahasa Melayu](Lecture-3-Rendering-HTML-ms)
# Lecture 3: Rendering HTML
![A centred heading, a paragraph and two images](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-3-html.png)
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.
+14
View File
@@ -0,0 +1,14 @@
[English](Lecture-4-Decorators) · **Dansk** · [Bahasa Melayu](Lecture-4-Decorators-ms)
# Lektion 4: Dekoratorer
![Ruten /bye i fed, fremhævet og understreget tekst](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-4-decorators.png)
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.
+14
View File
@@ -0,0 +1,14 @@
[English](Lecture-4-Decorators) · [Dansk](Lecture-4-Decorators-da) · **Bahasa Melayu**
# Pelajaran 4: Penghias
![Laluan /bye dengan teks tebal, ditekankan dan bergaris bawah](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-4-decorators.png)
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.
+14
View File
@@ -0,0 +1,14 @@
**English** · [Dansk](Lecture-4-Decorators-da) · [Bahasa Melayu](Lecture-4-Decorators-ms)
# Lecture 4: Decorators
![The route /bye in bold, emphasised and underlined text](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-4-decorators.png)
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
![Angela er logget ind og får et opslag, Bob er ikke og får intet](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-5-authenticated.png)
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
![Angela telah log masuk dan mendapat catatan, Bob tidak dan tidak mendapat apa-apa](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-5-authenticated.png)
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
![Angela is logged in and gets a post, Bob is not and gets none](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-5-authenticated.png)
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.
+17
View File
@@ -0,0 +1,17 @@
[English](Lecture-6-Final-Project) · **Dansk** · [Bahasa Melayu](Lecture-6-Final-Project-ms)
# Lektion 6: Slutprojekt
![3 er for lavt](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-too-low.png)
![7 er for højt](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-too-high.png)
![5 er rigtigt](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-correct.png)
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.
+17
View File
@@ -0,0 +1,17 @@
[English](Lecture-6-Final-Project) · [Dansk](Lecture-6-Final-Project-da) · **Bahasa Melayu**
# Pelajaran 6: Projek akhir
![3 terlalu rendah](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-too-low.png)
![7 terlalu tinggi](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-too-high.png)
![5 betul](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-correct.png)
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.
+17
View File
@@ -0,0 +1,17 @@
**English** · [Dansk](Lecture-6-Final-Project-da) · [Bahasa Melayu](Lecture-6-Final-Project-ms)
# Lecture 6: Final project
![3 is too low](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-too-low.png)
![7 is too high](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-too-high.png)
![5 is correct](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-correct.png)
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.
+32
View File
@@ -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.
+32
View File
@@ -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.
+32
View File
@@ -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.
+16
View File
@@ -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)
+71
View File
@@ -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")))
+14
View File
@@ -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"