Skip to content

Commit 1f48b72

Browse files
Merge pull request #98 from Promptly-Technologies-LLC/96-refactor-headers-and-headings-levels-in-customizationqmd
Refactor customization.md for clearer topic separation
2 parents 19019b2 + ab2e625 commit 1f48b72

File tree

1 file changed

+144
-134
lines changed

1 file changed

+144
-134
lines changed

docs/customization.qmd

Lines changed: 144 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -61,26 +61,32 @@ One use case for this file, if using the Cursor IDE, is to rename it to `.cursor
6161

6262
We have also exposed the full Markdown-formatted project documentation as a [single text file](static/documentation.txt) for easy downloading and embedding for RAG workflows.
6363

64-
## Project structure
64+
## Application architecture
6565

66-
### Customizable folders and files
66+
### Post-Redirect-Get pattern
67+
68+
In this template, we use FastAPI to define the "API endpoints" of our application. An API endpoint is simply a URL that accepts user requests and returns responses. When a user visits a page, their browser sends what's called a "GET" request to an endpoint, and the server processes it (often querying a database), and returns a response (typically HTML). The browser renders the HTML, displaying the page.
69+
70+
We also create POST endpoints, which accept form submissions so the user can create, update, and delete data in the database. This template follows the Post-Redirect-Get (PRG) pattern to handle POST requests. When a form is submitted, the server processes the data and then returns a "redirect" response, which sends the user to a GET endpoint to re-render the page with the updated data. (See [Architecture](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/architecture.html) for more details.)
71+
72+
#### Customizable folders and files
6773

6874
- FastAPI application entry point and GET routes: `main.py`
6975
- FastAPI POST routes: `routers/`
70-
- User authentication endpoints: `auth.py`
76+
- User authentication endpoints: `authentication.py`
7177
- User profile management endpoints: `user.py`
7278
- Organization management endpoints: `organization.py`
7379
- Role management endpoints: `role.py`
7480
- Jinja2 templates: `templates/`
75-
- Bootstrap Sass Files: `scss/`
7681
- Static assets: `static/`
7782
- Unit tests: `tests/`
7883
- Test database configuration: `docker-compose.yml`
7984
- Helper functions: `utils/`
8085
- Auth helpers: `auth.py`
8186
- Database helpers: `db.py`
8287
- Database models: `models.py`
83-
- Environment variables: `.env`
88+
- Image helpers: `images.py`
89+
- Environment variables: `.env.example`
8490
- CI/CD configuration: `.github/`
8591
- Project configuration: `pyproject.toml`
8692
- Quarto documentation:
@@ -89,15 +95,11 @@ We have also exposed the full Markdown-formatted project documentation as a [sin
8995

9096
Most everything else is auto-generated and should not be manually modified.
9197

92-
### Defining a web backend with FastAPI
98+
## Backend
9399

94-
We use FastAPI to define the "API endpoints" of our application. An API endpoint is simply a URL that accepts user requests and returns responses. When a user visits a page, their browser sends what's called a "GET" request to an endpoint, and the server processes it (often querying a database), and returns a response (typically HTML). The browser renders the HTML, displaying the page.
100+
### Code conventions
95101

96-
We also create POST endpoints, which accept form submissions so the user can create, update, and delete data in the database. This template follows the Post-Redirect-Get (PRG) pattern to handle POST requests. When a form is submitted, the server processes the data and then returns a "redirect" response, which sends the user to a GET endpoint to re-render the page with the updated data. (See [Architecture](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/architecture.html) for more details.)
97-
98-
#### Routing patterns in this template
99-
100-
In this template, GET routes are defined in the main entry point for the application, `main.py`. POST routes are organized into separate modules within the `routers/` directory.
102+
GET routes are defined in the main entry point for the application, `main.py`. POST routes are organized into separate modules within the `routers/` directory.
101103

102104
We name our GET routes using the convention `read_<name>`, where `<name>` is the name of the page, to indicate that they are read-only endpoints that do not modify the database.
103105

@@ -111,108 +113,7 @@ Some of our routes take request parameters, which we pass as keyword arguments t
111113

112114
Some parameters are shared across all authenticated or unauthenticated routes, so we define them in the `common_authenticated_parameters` and `common_unauthenticated_parameters` dependencies defined in `main.py`.
113115

114-
### HTML templating with Jinja2
115-
116-
To generate the HTML pages to be returned from our GET routes, we use Jinja2 templates. Jinja2's hierarchical templates allow creating a base template (`templates/base.html`) that defines the overall layout of our web pages (e.g., where the header, body, and footer should go). Individual pages can then extend this base template. We can also template reusable components that can be injected into our layout or page templates.
117-
118-
With Jinja2, we can use the `{% block %}` tag to define content blocks, and the `{% extends %}` tag to extend a base template. We can also use the `{% include %}` tag to include a component in a parent template. See the [Jinja2 documentation on template inheritance](https://jinja.palletsprojects.com/en/stable/templates/#template-inheritance) for more details.
119-
120-
### Custom theming with Bootstrap Sass
121-
122-
[Install Node.js](https://nodejs.org/en/download/) on your local machine if it is not there already.
123-
124-
Install `bootstrap`, `sass`, `gulp`, and `gulp-sass` in your project:
125-
126-
```bash
127-
npm install --save-dev bootstrap sass gulp gulp-cli gulp-sass
128-
```
129-
130-
This will create a `node_modules` folder, a `package-lock.json` file, and a `package.json` file in the root directory of the project.
131-
132-
Create an `scss` folder and a basic `scss/styles.scss` file:
133-
134-
```bash
135-
mkdir scss
136-
touch scss/styles.scss
137-
```
138-
139-
Your custom styles will go in `scss/styles.scss`, along with `@import` statements to include the Bootstrap components you want. For example, the default CSS for the template was compiled from the following configuration, which imports all of Bootstrap and overrides the `$theme-colors` and `$font-family-base` variables:
140-
141-
```scss
142-
// styles.scss
143-
144-
// Include any default variable overrides here (functions won't be available)
145-
146-
// State colors
147-
$primary: #7464a1;
148-
$secondary: #64a19d;
149-
$success: #67c29c;
150-
$info: #1cabc4;
151-
$warning: #e4c662;
152-
$danger: #a16468;
153-
$light: #f8f9fa;
154-
$dark: #343a40;
155-
156-
// Bootstrap color map
157-
$theme-colors: (
158-
"primary": $primary,
159-
"secondary": $secondary,
160-
"success": $success,
161-
"info": $info,
162-
"warning": $warning,
163-
"danger": $danger,
164-
"light": $light,
165-
"dark": $dark
166-
);
167-
168-
$font-family-base: (
169-
"Nunito",
170-
-apple-system,
171-
BlinkMacSystemFont,
172-
"Segoe UI",
173-
Roboto,
174-
"Helvetica Neue",
175-
Arial,
176-
sans-serif,
177-
"Apple Color Emoji",
178-
"Segoe UI Emoji",
179-
"Segoe UI Symbol",
180-
"Noto Color Emoji"
181-
);
182-
183-
// Include all of Bootstrap
184-
185-
@import "../node_modules/bootstrap/scss/bootstrap";
186-
```
187-
188-
The most common use case for `styles.scss` is to define a custom color scheme and fonts, but it's also possible to other visual details such as border radius and box shadow depth. See the [Bootstrap Sass customization instructions](https://getbootstrap.com/docs/5.3/customize/sass/) and the many free templates available at [Start Bootstrap](https://startbootstrap.com) for examples.
189-
190-
To compile the Sass files, we use `gulp`. In the project root directory, create a `gulpfile.js` file with the following content:
191-
192-
```javascript
193-
const gulp = require('gulp');
194-
const sass = require('gulp-sass')(require('sass'));
195-
196-
// Define a task to compile Sass
197-
gulp.task('sass', function() {
198-
return gulp.src('scss/**/*.scss') // Source folder containing Sass files
199-
.pipe(sass().on('error', sass.logError))
200-
.pipe(gulp.dest('static/css')); // Destination folder for compiled CSS
201-
});
202-
203-
// Define a default task
204-
gulp.task('default', gulp.series('sass'));
205-
```
206-
207-
To compile the Sass file to `static/css`, run this command:
208-
209-
```bash
210-
npx gulp
211-
```
212-
213-
Note that this will overwrite the existing `static/css/styles.css` file, so if you want to define any custom CSS styles, you should do so in either the `scss/styles.scss` file or in `static/css/extras.css`.
214-
215-
#### Context variables
116+
### Context variables
216117

217118
Context refers to Python variables passed to a template to populate the HTML. In a FastAPI GET route, we can pass context to a template using the `templates.TemplateResponse` method, which takes the request and any context data as arguments. For example:
218119

@@ -227,20 +128,7 @@ async def welcome(request: Request):
227128

228129
In this example, the `welcome.html` template will receive two pieces of context: the user's `request`, which is always passed automatically by FastAPI, and a `username` variable, which we specify as "Alice". We can then use the `{{{ username }}}` syntax in the `welcome.html` template (or any of its parent or child templates) to insert the value into the HTML.
229130

230-
#### Form validation strategy
231-
232-
While this template includes comprehensive server-side validation through Pydantic models and custom validators, it's important to note that server-side validation should be treated as a fallback security measure. If users ever see the `validation_error.html` template, it indicates that our client-side validation has failed to catch invalid input before it reaches the server.
233-
234-
Best practices dictate implementing thorough client-side validation via JavaScript and/or HTML `input` element `pattern` attributes to:
235-
236-
- Provide immediate feedback to users
237-
- Reduce server load
238-
- Improve user experience by avoiding round-trips to the server
239-
- Prevent malformed data from ever reaching the backend
240-
241-
Server-side validation remains essential as a security measure against malicious requests that bypass client-side validation, but it should rarely be encountered during normal user interaction. See `templates/authentication/register.html` for a client-side form validation example involving both JavaScript and HTML regex `pattern` matching.
242-
243-
#### Email templating
131+
### Email templating
244132

245133
Password reset and other transactional emails are also handled through Jinja2 templates, located in the `templates/emails` directory. The email templates follow the same inheritance pattern as web templates, with `base_email.html` providing the common layout and styling.
246134

@@ -250,7 +138,7 @@ Here's how the default password reset email template looks:
250138

251139
The email templates use inline CSS styles to ensure consistent rendering across email clients. Like web templates, they can receive context variables from the Python code (such as `reset_url` in the password reset template).
252140

253-
### Writing type annotated code
141+
### Server-side form validation
254142

255143
Pydantic is used for data validation and serialization. It ensures that the data received in requests meets the expected format and constraints. Pydantic models are used to define the structure of request and response data, making it easy to validate and parse JSON payloads.
256144

@@ -324,7 +212,7 @@ class UserRegister(BaseModel):
324212
)
325213
```
326214

327-
#### Middleware exception handling
215+
### Middleware exception handling
328216

329217
Middlewares—which process requests before they reach the route handlers and responses before they are sent back to the client—are defined in `main.py`. They are commonly used in web development for tasks such as error handling, authentication token validation, logging, and modifying request/response objects.
330218

@@ -348,11 +236,11 @@ async def password_mismatch_exception_handler(request: Request, exc: PasswordMis
348236
)
349237
```
350238

351-
### Database configuration and access with SQLModel
239+
## Database configuration and access with SQLModel
352240

353241
SQLModel is an Object-Relational Mapping (ORM) library that allows us to interact with our PostgreSQL database using Python classes instead of writing raw SQL. It combines the features of SQLAlchemy (a powerful database toolkit) with Pydantic's data validation.
354242

355-
#### Models and relationships
243+
### Models and relationships
356244

357245
Our database models are defined in `utils/models.py`. Each model is a Python class that inherits from `SQLModel` and represents a database table. The key models are:
358246

@@ -397,7 +285,7 @@ graph.write_png('static/schema.png')
397285
![Database Schema](static/schema.png)
398286

399287

400-
#### Database helpers
288+
### Database helpers
401289

402290
Database operations are facilitated by helper functions in `utils/db.py`. Key functions include:
403291

@@ -427,7 +315,7 @@ user.has_permission(permission, organization)
427315

428316
You should create custom `ValidPermissions` enum values for your application and validate that users have the necessary permissions before allowing them to modify organization data resources.
429317

430-
#### Cascade deletes
318+
### Cascade deletes
431319

432320
Cascade deletes (in which deleting a record from one table deletes related records from another table) can be handled at either the ORM level or the database level. This template handles cascade deletes at the ORM level, via SQLModel relationships. Inside a SQLModel `Relationship`, we set:
433321

@@ -453,3 +341,125 @@ for role in session.exec(select(Role)).all():
453341
```
454342

455343
This is slower than deleting the records directly, but it makes [many-to-many relationships](https://sqlmodel.tiangolo.com/tutorial/many-to-many/create-models-with-link/#create-the-tables) much easier to manage.
344+
345+
## Frontend
346+
347+
### HTML templating with Jinja2
348+
349+
To generate the HTML pages to be returned from our GET routes, we use Jinja2 templates. Jinja2's hierarchical templates allow creating a base template (`templates/base.html`) that defines the overall layout of our web pages (e.g., where the header, body, and footer should go). Individual pages can then extend this base template. We can also template reusable components that can be injected into our layout or page templates.
350+
351+
With Jinja2, we can use the `{% block %}` tag to define content blocks, and the `{% extends %}` tag to extend a base template. We can also use the `{% include %}` tag to include a component in a parent template. See the [Jinja2 documentation on template inheritance](https://jinja.palletsprojects.com/en/stable/templates/#template-inheritance) for more details.
352+
353+
### Custom theming with Bootstrap
354+
355+
[Install Node.js](https://nodejs.org/en/download/) on your local machine if it is not there already.
356+
357+
Install `bootstrap`, `sass`, `gulp`, and `gulp-sass` in your project:
358+
359+
```bash
360+
npm install --save-dev bootstrap sass gulp gulp-cli gulp-sass
361+
```
362+
363+
This will create a `node_modules` folder, a `package-lock.json` file, and a `package.json` file in the root directory of the project.
364+
365+
Create an `scss` folder and a basic `scss/styles.scss` file:
366+
367+
```bash
368+
mkdir scss
369+
touch scss/styles.scss
370+
```
371+
372+
Your custom styles will go in `scss/styles.scss`, along with `@import` statements to include the Bootstrap components you want.
373+
374+
#### Customizing the Bootstrap SCSS
375+
376+
The default CSS for the template was compiled from the following `scss/styles.scss` configuration, which imports all of Bootstrap and overrides the `$theme-colors` and `$font-family-base` variables:
377+
378+
```scss
379+
// styles.scss
380+
381+
// Include any default variable overrides here (functions won't be available)
382+
383+
// State colors
384+
$primary: #7464a1;
385+
$secondary: #64a19d;
386+
$success: #67c29c;
387+
$info: #1cabc4;
388+
$warning: #e4c662;
389+
$danger: #a16468;
390+
$light: #f8f9fa;
391+
$dark: #343a40;
392+
393+
// Bootstrap color map
394+
$theme-colors: (
395+
"primary": $primary,
396+
"secondary": $secondary,
397+
"success": $success,
398+
"info": $info,
399+
"warning": $warning,
400+
"danger": $danger,
401+
"light": $light,
402+
"dark": $dark
403+
);
404+
405+
$font-family-base: (
406+
"Nunito",
407+
-apple-system,
408+
BlinkMacSystemFont,
409+
"Segoe UI",
410+
Roboto,
411+
"Helvetica Neue",
412+
Arial,
413+
sans-serif,
414+
"Apple Color Emoji",
415+
"Segoe UI Emoji",
416+
"Segoe UI Symbol",
417+
"Noto Color Emoji"
418+
);
419+
420+
// Include all of Bootstrap
421+
422+
@import "../node_modules/bootstrap/scss/bootstrap";
423+
```
424+
425+
The most common use case for `styles.scss` is to define a custom color scheme and fonts, but it's also possible to customize some other visual details such as border radius and box shadow depth. See the [Bootstrap Sass customization documentation](https://getbootstrap.com/docs/5.3/customize/sass/) and the many free templates available at [Start Bootstrap](https://startbootstrap.com) for examples.
426+
427+
#### Compiling the SCSS to CSS
428+
429+
To compile the SCSS files to CSS, we use `gulp`. In the project root directory, create a `gulpfile.js` file with the following content:
430+
431+
```javascript
432+
const gulp = require('gulp');
433+
const sass = require('gulp-sass')(require('sass'));
434+
435+
// Define a task to compile Sass
436+
gulp.task('sass', function() {
437+
return gulp.src('scss/**/*.scss') // Source folder containing Sass files
438+
.pipe(sass().on('error', sass.logError))
439+
.pipe(gulp.dest('static/css')); // Destination folder for compiled CSS
440+
});
441+
442+
// Define a default task
443+
gulp.task('default', gulp.series('sass'));
444+
```
445+
446+
To compile the SCSS file to `static/css`, run this command:
447+
448+
```bash
449+
npx gulp
450+
```
451+
452+
Note that this will overwrite the existing `static/css/styles.css` file, so if you want to define any custom CSS styles, you should do so in either the `scss/styles.scss` file or in `static/css/extras.css`.
453+
454+
### Client-side form validation
455+
456+
While this template includes comprehensive server-side validation through Pydantic models and custom validators, it's important to note that server-side validation should be treated as a fallback security measure. If users ever see the `validation_error.html` template, it indicates that our client-side validation has failed to catch invalid input before it reaches the server.
457+
458+
Best practices dictate implementing thorough client-side validation via JavaScript and/or HTML `input` element `pattern` attributes to:
459+
460+
- Provide immediate feedback to users
461+
- Reduce server load
462+
- Improve user experience by avoiding round-trips to the server
463+
- Prevent malformed data from ever reaching the backend
464+
465+
Server-side validation remains essential as a security measure against malicious requests that bypass client-side validation, but it should rarely be encountered during normal user interaction. See `templates/authentication/register.html` for a client-side form validation example involving both JavaScript and HTML regex `pattern` matching.

0 commit comments

Comments
 (0)