mirror of
https://github.com/askama-rs/askama.git
synced 2025-10-03 07:45:14 +00:00
Add comments
This commit is contained in:
parent
cf94c09e32
commit
fafd44e4fe
@ -2,19 +2,31 @@
|
|||||||
name = "actix-web-app"
|
name = "actix-web-app"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# This is an example application that uses both rinja as template engine,
|
||||||
|
# and actix-web as your web-framework.
|
||||||
|
# rinja_actix makes it easy to use rinja templates as `Responder` of an actix-web request.
|
||||||
|
# The rendered template is simply the response of your handler!
|
||||||
rinja_actix = { version = "0.15.0", path = "../../rinja_actix" }
|
rinja_actix = { version = "0.15.0", path = "../../rinja_actix" }
|
||||||
|
|
||||||
actix-web = { version = "4.8.0", default-features = false, features = ["macros"] }
|
actix-web = { version = "4.8.0", default-features = false, features = ["macros"] }
|
||||||
|
tokio = { version = "1.38.0", features = ["sync", "rt-multi-thread"] }
|
||||||
|
|
||||||
|
# serde and strum are used to parse (deserialize) and generate (serialize) information
|
||||||
|
# between web requests, e.g. to share the selected display language.
|
||||||
|
serde = { version = "1.0.203", features = ["derive"] }
|
||||||
|
strum = { version = "0.26.3", features = ["derive"] }
|
||||||
|
|
||||||
|
# These depenendies are simply used for a better user experience, having access logs in the
|
||||||
|
# console, and error messages if anything goes wrong, e.g. if the port is already in use.
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.3"
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
pretty-error-debug = "0.3.0"
|
pretty-error-debug = "0.3.0"
|
||||||
serde = { version = "1.0.203", features = ["derive"] }
|
|
||||||
strum = { version = "0.26.3", features = ["derive"] }
|
|
||||||
thiserror = "1.0.61"
|
thiserror = "1.0.61"
|
||||||
tokio = { version = "1.38.0", features = ["sync", "rt-multi-thread"] }
|
|
||||||
|
|
||||||
|
# In a real application you would not need this section. It is only used in here, so that this
|
||||||
|
# example can have a more lenient MSRV (minimum supported rust version) than rinja as a whole.
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["."]
|
members = ["."]
|
||||||
|
17
examples/actix-web-app/README.md
Normal file
17
examples/actix-web-app/README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# rinja + actix-web example web app
|
||||||
|
|
||||||
|
This is a simple web application that uses rinja as template engine, and
|
||||||
|
[actix-web](https://crates.io/crates/actix-web) as web framework.
|
||||||
|
It lets the user of the web page select a display language, and asks for their name.
|
||||||
|
The example shows the interaction between both projects, and serves as an example to use
|
||||||
|
basic rinja features such as base templates to a unified layout skeletton for your page,
|
||||||
|
and less boilerplate in your template code.
|
||||||
|
|
||||||
|
To run the example execute `cargo run` in this folder.
|
||||||
|
Once the project is running, open <http://127.0.0.1:8080/> in your browser.
|
||||||
|
To gracefully shut does the server, type ctrl+C in your terminal.
|
||||||
|
|
||||||
|
The files of the project contain comments for you to read.
|
||||||
|
The recommended reading order is "templates/_layout.html", "templates/index.html",
|
||||||
|
"Cargo.toml", "src/main.rs". Also please have a look at our [book](https://rinja.readthedocs.io/),
|
||||||
|
which explains rinja's features in greater detail.
|
@ -1,11 +1,12 @@
|
|||||||
use actix_web::http::{header, Method};
|
use actix_web::http::{header, Method};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
get, middleware, web, App, Either, HttpRequest, HttpResponse, HttpServer, Responder, Result,
|
get, middleware, web, App, HttpRequest, HttpResponse, HttpServer, Responder, Result,
|
||||||
};
|
};
|
||||||
use rinja_actix::Template;
|
use rinja_actix::Template;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::runtime;
|
use tokio::runtime;
|
||||||
|
|
||||||
|
// This function and the next mostly contains boiler plate to get an actix-web application running.
|
||||||
fn main() -> Result<(), Error> {
|
fn main() -> Result<(), Error> {
|
||||||
let env = env_logger::Env::new().default_filter_or("info");
|
let env = env_logger::Env::new().default_filter_or("info");
|
||||||
env_logger::try_init_from_env(env).map_err(Error::Log)?;
|
env_logger::try_init_from_env(env).map_err(Error::Log)?;
|
||||||
@ -19,6 +20,7 @@ fn main() -> Result<(), Error> {
|
|||||||
|
|
||||||
async fn amain() -> Result<(), Error> {
|
async fn amain() -> Result<(), Error> {
|
||||||
let server = HttpServer::new(|| {
|
let server = HttpServer::new(|| {
|
||||||
|
// This closure contains the setup of the routing rules of your app.
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default())
|
||||||
.wrap(middleware::NormalizePath::new(
|
.wrap(middleware::NormalizePath::new(
|
||||||
@ -29,6 +31,7 @@ async fn amain() -> Result<(), Error> {
|
|||||||
.service(greeting_handler)
|
.service(greeting_handler)
|
||||||
.default_service(web::to(not_found_handler))
|
.default_service(web::to(not_found_handler))
|
||||||
});
|
});
|
||||||
|
// In a real application you would most likely read the configuration from a config file.
|
||||||
let server = server.bind(("127.0.0.1", 8080)).map_err(Error::Bind)?;
|
let server = server.bind(("127.0.0.1", 8080)).map_err(Error::Bind)?;
|
||||||
for addr in server.addrs() {
|
for addr in server.addrs() {
|
||||||
println!("Listening on: http://{addr}/");
|
println!("Listening on: http://{addr}/");
|
||||||
@ -48,7 +51,20 @@ enum Error {
|
|||||||
Run(#[source] std::io::Error),
|
Run(#[source] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Deserialize, strum::Display, strum::AsRefStr)]
|
/// Using this type your user can select the display language of your page.
|
||||||
|
///
|
||||||
|
/// The same type is used by actix-web as part of the URL, and in rinja to select what content to
|
||||||
|
/// show, and also as an HTML attribute in `<html lang=`. To make it possible to use the same type
|
||||||
|
/// for three diffent use cases, we use a few derive macros:
|
||||||
|
///
|
||||||
|
/// * `Default` to have a default/fallback language.
|
||||||
|
/// * `Debug` is not strictly needed, but it might aid debugging.
|
||||||
|
/// * `Clone` + `Copy` so that we can pass the language by value.
|
||||||
|
/// * `PartialEq` so that we can use the type in comparisons with `==` or `!=`.
|
||||||
|
/// * `serde::Deserialize` so that actix-web can parse the type in incoming URLs.
|
||||||
|
/// * `strum::AsRefStr` so that actix-web the use the type to construct URL for printing.
|
||||||
|
/// * `strum::Display` so that rinja can write the value in templates.
|
||||||
|
#[derive(Default, Debug, Clone, Copy, PartialEq, Deserialize, strum::AsRefStr, strum::Display)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
enum Lang {
|
enum Lang {
|
||||||
#[default]
|
#[default]
|
||||||
@ -57,7 +73,13 @@ enum Lang {
|
|||||||
fr,
|
fr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is your "Error: 404 - not found" handler
|
||||||
async fn not_found_handler(req: HttpRequest) -> Result<impl Responder> {
|
async fn not_found_handler(req: HttpRequest) -> Result<impl Responder> {
|
||||||
|
// It uses a rinja template to display its content.
|
||||||
|
// The member `req` contains the request, and is used e.g. to generate URLs in our template.
|
||||||
|
// The member `lang` is used by "_layout.html" which "404.html" extends. Even though it
|
||||||
|
// is always the fallback language English in here, "_layout.html" expects to be able to access
|
||||||
|
// this field, so you have to provide it.
|
||||||
#[derive(Debug, Template)]
|
#[derive(Debug, Template)]
|
||||||
#[template(path = "404.html")]
|
#[template(path = "404.html")]
|
||||||
struct Tmpl {
|
struct Tmpl {
|
||||||
@ -65,35 +87,62 @@ async fn not_found_handler(req: HttpRequest) -> Result<impl Responder> {
|
|||||||
lang: Lang,
|
lang: Lang,
|
||||||
}
|
}
|
||||||
|
|
||||||
match req.method() {
|
if req.method() == Method::GET {
|
||||||
&Method::GET => Ok(Either::Left(Tmpl {
|
// In here we have to render the result to a string manually, because we don't want to
|
||||||
|
// generate a "status 200" result, but "status 404". In other cases you can simply return
|
||||||
|
// the template, wrapped in `Ok()`, and the request gets generated with "status 200",
|
||||||
|
// and the right MIME type.
|
||||||
|
let tmpl = Tmpl {
|
||||||
req,
|
req,
|
||||||
lang: Lang::default(),
|
lang: Lang::default(),
|
||||||
})),
|
};
|
||||||
_ => Ok(Either::Right(HttpResponse::MethodNotAllowed().finish())),
|
// The MIME type was derived by rinja by the extension of the template file.
|
||||||
|
Ok(HttpResponse::NotFound()
|
||||||
|
.append_header((header::CONTENT_TYPE, Tmpl::MIME_TYPE))
|
||||||
|
.body(tmpl.to_string()))
|
||||||
|
} else {
|
||||||
|
Ok(HttpResponse::MethodNotAllowed().finish())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The is first page your user hits does not contain language infomation, so we redirect them
|
||||||
|
/// to a URL that does contain the default language.
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
async fn start_handler(req: HttpRequest) -> Result<impl Responder> {
|
async fn start_handler(req: HttpRequest) -> Result<impl Responder> {
|
||||||
|
// This example show how the type `Lang` can be used to construct a URL in actix-web.
|
||||||
let url = req.url_for("index_handler", [Lang::default()])?;
|
let url = req.url_for("index_handler", [Lang::default()])?;
|
||||||
Ok(HttpResponse::Found()
|
Ok(HttpResponse::Found()
|
||||||
.insert_header((header::LOCATION, url.as_str()))
|
.insert_header((header::LOCATION, url.as_str()))
|
||||||
.finish())
|
.finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This type collects the query parameter `?name=` (if present)
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct IndexHandlerQuery {
|
struct IndexHandlerQuery {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is the first localized page your user sees.
|
||||||
|
///
|
||||||
|
/// It has arguments in the path that need to be parsable using `serde::Deserialize`;
|
||||||
|
/// see `Lang` for an explanation.
|
||||||
|
/// And also query parameters (anything after `?` in the incoming URL).
|
||||||
#[get("/{lang}/index.html")]
|
#[get("/{lang}/index.html")]
|
||||||
async fn index_handler(
|
async fn index_handler(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
path: web::Path<(Lang,)>,
|
path: web::Path<(Lang,)>,
|
||||||
web::Query(query): web::Query<IndexHandlerQuery>,
|
web::Query(query): web::Query<IndexHandlerQuery>,
|
||||||
) -> Result<impl Responder> {
|
) -> Result<impl Responder> {
|
||||||
|
// Same as in `not_found_handler`, we have `req` to build URLs in the template, and
|
||||||
|
// `lang` to select the display language. In the template we both use `{% match lang %}` and
|
||||||
|
// `{% if lang !=`, the former to select the text of a specific language, e.g. in the `<title>`;
|
||||||
|
// and the latter to display references to all other available languages except the currently
|
||||||
|
// selected one.
|
||||||
|
// The field `name` will contain the value of the query paramater of the same name.
|
||||||
|
// In `IndexHandlerQuery` we annotated the field with `#[serde(default)]`, so if the value is
|
||||||
|
// absent, an empty string is selected by default, which is visible to the user an empty
|
||||||
|
// `<input type="text" />` element.
|
||||||
#[derive(Debug, Template)]
|
#[derive(Debug, Template)]
|
||||||
#[template(path = "index.html")]
|
#[template(path = "index.html")]
|
||||||
struct Tmpl {
|
struct Tmpl {
|
||||||
@ -115,6 +164,11 @@ struct GreetingHandlerQuery {
|
|||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is the final page of this example application.
|
||||||
|
///
|
||||||
|
/// Like `index_handler` it contains a language in the URL, and a query parameter to read the user's
|
||||||
|
/// provided name. In here, the query argument `name` has no default value, so actix-web will show
|
||||||
|
/// an error message if absent.
|
||||||
#[get("/{lang}/greet-me.html")]
|
#[get("/{lang}/greet-me.html")]
|
||||||
async fn greeting_handler(
|
async fn greeting_handler(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
|
56
examples/actix-web-app/templates/_layout.css
Normal file
56
examples/actix-web-app/templates/_layout.css
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
{#-
|
||||||
|
This file is included by "_layout.html".
|
||||||
|
You can still use template syntax (such as this comment) in here.
|
||||||
|
-#}
|
||||||
|
|
||||||
|
html {
|
||||||
|
background-color: #eee;
|
||||||
|
color: #111;
|
||||||
|
font-size: 62.5%;
|
||||||
|
min-height: 100vh;
|
||||||
|
color-scheme: light;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
line-height: 1.2em;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: #fff;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
max-width: 40em;
|
||||||
|
margin: 1em auto;
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
h1 { font-size: 2.4rem; }
|
||||||
|
h2 { font-size: 2.2rem; }
|
||||||
|
h3 { font-size: 2.0rem; }
|
||||||
|
a:link, a:visited {
|
||||||
|
color: #36c;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:active, a:hover, a:focus {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 0.3em;
|
||||||
|
}
|
||||||
|
#lang-select {
|
||||||
|
font-size: 80%;
|
||||||
|
width: max-content;
|
||||||
|
margin: 2em 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
#lang-select li {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-basis: auto;
|
||||||
|
margin: .25em 0 0 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
text-align: center;
|
||||||
|
list-style-type: none;
|
||||||
|
border-left: 0.1rem solid currentColor;
|
||||||
|
}
|
||||||
|
#lang-select li:first-of-type {
|
||||||
|
border-left: 0 none transparent;
|
||||||
|
}
|
||||||
|
#lang-select li:last-of-type {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
@ -1,67 +1,49 @@
|
|||||||
|
{#-
|
||||||
|
This is the basic layout of our example application.
|
||||||
|
It is the core skeletton shared between all pages.
|
||||||
|
It expects the struct of any template that `{% extends %}` this layout to contain
|
||||||
|
(at least) a field `lang: Lang`, so it can be used in the `<html lang=` attribute.
|
||||||
|
-#}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{lang}}">
|
<html lang="{{lang}}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
{#-
|
||||||
|
A base template can contain `blocks`, which my be overridden templates that use
|
||||||
|
this base template. A block may contain a default content, if the extending
|
||||||
|
template does want to / need to override the content of a block.
|
||||||
|
|
||||||
|
E.g. maybe you would like to have "Rinja example application" as default title for
|
||||||
|
your pages, then simply add this text (without quotation marks) in the block!
|
||||||
|
|
||||||
|
The default content can be as complex as you need to to be.
|
||||||
|
E.g. it may contain any nodes like `{% if … %}`, and even other blocks.
|
||||||
|
~#}
|
||||||
<title>{% block title %}{% endblock %}</title>
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
|
||||||
<meta http-equiv="expires" content="Sat, 01 Dec 2001 00:00:00 GMT" />
|
<meta http-equiv="expires" content="Sat, 01 Dec 2001 00:00:00 GMT" />
|
||||||
<meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate" />
|
<meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate" />
|
||||||
<meta http-equiv="pragma" content="no-cache" />
|
<meta http-equiv="pragma" content="no-cache" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="robots" content="noindex, nofollow" />
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
|
||||||
|
{#-
|
||||||
|
In a real application you most likely would want to link style sheets,
|
||||||
|
any JavaScripts etc. using e.g. `actix-files`, instead of embedding the content
|
||||||
|
in your generated HTML.
|
||||||
|
|
||||||
|
As you can see, this comment starts with `-`, which will tells the comment
|
||||||
|
to strip all white spaces before it, until it finds the first non-white space
|
||||||
|
character, a `>`.
|
||||||
|
|
||||||
|
The comment is also terminated with `~`. This also strips white spaces, but
|
||||||
|
will leave one space, or a newline character, if the stripped content contained
|
||||||
|
a newline.
|
||||||
|
~#}
|
||||||
<style>
|
<style>
|
||||||
/*<![CDATA[*/
|
/*<![CDATA[*/
|
||||||
html {
|
{%~ include "_layout.css" ~%}
|
||||||
background-color: #eee;
|
|
||||||
color: #111;
|
|
||||||
font-size: 62.5%;
|
|
||||||
min-height: 100vh;
|
|
||||||
color-scheme: light;
|
|
||||||
}
|
|
||||||
* {
|
|
||||||
line-height: 1.2em;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
background-color: #fff;
|
|
||||||
font-size: 1.8rem;
|
|
||||||
max-width: 40em;
|
|
||||||
margin: 1em auto;
|
|
||||||
padding: 2em;
|
|
||||||
}
|
|
||||||
h1 { font-size: 2.4rem; }
|
|
||||||
h2 { font-size: 2.2rem; }
|
|
||||||
h3 { font-size: 2.0rem; }
|
|
||||||
a:link, a:visited {
|
|
||||||
color: #36c;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
a:active, a:hover, a:focus {
|
|
||||||
text-decoration: underline;
|
|
||||||
text-underline-offset: 0.3em;
|
|
||||||
}
|
|
||||||
#lang-select {
|
|
||||||
font-size: 80%;
|
|
||||||
width: max-content;
|
|
||||||
margin: 2em 0 0 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
#lang-select li {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-basis: auto;
|
|
||||||
margin: .25em 0 0 0;
|
|
||||||
padding: 0 1em;
|
|
||||||
text-align: center;
|
|
||||||
list-style-type: none;
|
|
||||||
border-left: 0.1rem solid currentColor;
|
|
||||||
}
|
|
||||||
#lang-select li:first-of-type {
|
|
||||||
border-left: 0 none transparent;
|
|
||||||
}
|
|
||||||
#lang-select li:last-of-type {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
/*]]>*/
|
/*]]>*/
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
{% extends "_layout.html" %}
|
{% extends "_layout.html" %}
|
||||||
|
|
||||||
{%- block title -%}
|
{%- block title -%}
|
||||||
|
{#-
|
||||||
|
In here you can see how to use the language URL compment to select the text to display.
|
||||||
|
-#}
|
||||||
{%- match lang -%}
|
{%- match lang -%}
|
||||||
{%- when Lang::en -%} Hello!
|
{%- when Lang::en -%} Hello!
|
||||||
{%- when Lang::de -%} Hallo!
|
{%- when Lang::de -%} Hallo!
|
||||||
@ -16,12 +19,27 @@
|
|||||||
{%- when Lang::fr -%} Bonjour!
|
{%- when Lang::fr -%} Bonjour!
|
||||||
{%- endmatch -%}
|
{%- endmatch -%}
|
||||||
</h1>
|
</h1>
|
||||||
|
{#-
|
||||||
|
The `action` URL is built by actix-web, by using the user request `req` and
|
||||||
|
the language component of the URL. Both are fields in the struct that uses
|
||||||
|
this template file.
|
||||||
|
~#}
|
||||||
<form
|
<form
|
||||||
method="GET"
|
method="GET"
|
||||||
action="{{ req.url_for("greeting_handler", [lang])? }}"
|
action="{{ req.url_for("greeting_handler", [lang])? }}"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
|
{#-
|
||||||
|
If your text contains long lines, you may want to split them,
|
||||||
|
so you as a developer have an easier time reading them.
|
||||||
|
|
||||||
|
If you don't want to end up with a multitude of spaces in the
|
||||||
|
generated content, you can use empty comments as seen below: `#-~#`.
|
||||||
|
This would strip the space before the comment, and leave a newline
|
||||||
|
character after the comment. Another option would be `#~-#`,
|
||||||
|
so that single space remains.
|
||||||
|
-#}
|
||||||
{%- match lang -%}
|
{%- match lang -%}
|
||||||
{%- when Lang::en -%}
|
{%- when Lang::en -%}
|
||||||
I would like to say <em>hello</em>. {#-~#}
|
I would like to say <em>hello</em>. {#-~#}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user