code: upgrade to axum 0.7, working on repo info system

This commit is contained in:
Gabor Körber 2024-01-26 11:26:47 +01:00
parent 0adae154c8
commit 9f4067e94c
11 changed files with 389 additions and 82 deletions

238
Cargo.lock generated
View File

@ -79,9 +79,9 @@ dependencies = [
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.5" version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
@ -364,13 +364,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum-core", "axum-core 0.3.4",
"bitflags 1.3.2", "bitflags 1.3.2",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http 0.2.9",
"http-body", "http-body 0.4.5",
"hyper", "hyper 0.14.27",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"sync_wrapper",
"tokio",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e"
dependencies = [
"async-trait",
"axum-core 0.4.3",
"bytes",
"futures-util",
"http 1.0.0",
"http-body 1.0.0",
"http-body-util",
"hyper 1.1.0",
"hyper-util",
"itoa", "itoa",
"matchit", "matchit",
"memchr", "memchr",
@ -387,6 +417,7 @@ dependencies = [
"tower", "tower",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing",
] ]
[[package]] [[package]]
@ -398,14 +429,35 @@ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http 0.2.9",
"http-body", "http-body 0.4.5",
"mime", "mime",
"rustversion", "rustversion",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
] ]
[[package]]
name = "axum-core"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http 1.0.0",
"http-body 1.0.0",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.69" version = "0.3.69"
@ -821,16 +873,26 @@ dependencies = [
] ]
[[package]] [[package]]
name = "env_logger" name = "env_filter"
version = "0.10.1" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
dependencies = [ dependencies = [
"humantime",
"is-terminal",
"log", "log",
"regex", "regex",
"termcolor", ]
[[package]]
name = "env_logger"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eeb342678d785662fd2514be38c459bb925f02b68dd2a3e0f21d7ef82d979dd"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime",
"log",
] ]
[[package]] [[package]]
@ -1158,6 +1220,25 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "h2"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http 1.0.0",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.3" version = "0.12.3"
@ -1245,6 +1326,17 @@ dependencies = [
"itoa", "itoa",
] ]
[[package]]
name = "http"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]] [[package]]
name = "http-body" name = "http-body"
version = "0.4.5" version = "0.4.5"
@ -1252,7 +1344,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http 0.2.9",
"pin-project-lite",
]
[[package]]
name = "http-body"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
dependencies = [
"bytes",
"http 1.0.0",
]
[[package]]
name = "http-body-util"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840"
dependencies = [
"bytes",
"futures-util",
"http 1.0.0",
"http-body 1.0.0",
"pin-project-lite", "pin-project-lite",
] ]
@ -1284,8 +1399,8 @@ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-util", "futures-util",
"http", "http 0.2.9",
"http-body", "http-body 0.4.5",
"httparse", "httparse",
"httpdate", "httpdate",
"itoa", "itoa",
@ -1297,6 +1412,43 @@ dependencies = [
"want", "want",
] ]
[[package]]
name = "hyper"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"h2",
"http 1.0.0",
"http-body 1.0.0",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"tokio",
]
[[package]]
name = "hyper-util"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdea9aac0dbe5a9240d68cfd9501e2db94222c6dc06843e06640b9e07f0fdc67"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http 1.0.0",
"http-body 1.0.0",
"hyper 1.1.0",
"pin-project-lite",
"socket2 0.5.4",
"tokio",
"tracing",
]
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.58" version = "0.1.58"
@ -1391,17 +1543,6 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "is-terminal"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
dependencies = [
"hermit-abi",
"rustix 0.38.28",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.11.0" version = "0.11.0"
@ -1581,9 +1722,9 @@ dependencies = [
[[package]] [[package]]
name = "minijinja" name = "minijinja"
version = "1.0.8" version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80084fa3099f58b7afab51e5f92e24c2c2c68dcad26e96ad104bd6011570461d" checksum = "431d72874542d43aba1ca605870eacab134fdeb0c8fe27666ecf4b2662239df2"
dependencies = [ dependencies = [
"memo-map", "memo-map",
"percent-encoding", "percent-encoding",
@ -1612,7 +1753,7 @@ name = "miniweb"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum 0.7.4",
"barrel", "barrel",
"dotenvy", "dotenvy",
"dunce", "dunce",
@ -1630,6 +1771,7 @@ dependencies = [
"serde_json", "serde_json",
"slug", "slug",
"sqlformat", "sqlformat",
"strinto",
"tokio", "tokio",
] ]
@ -2279,7 +2421,7 @@ version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40"
dependencies = [ dependencies = [
"axum", "axum 0.6.20",
"rust-embed-impl", "rust-embed-impl",
"rust-embed-utils", "rust-embed-utils",
"tokio", "tokio",
@ -3013,6 +3155,15 @@ dependencies = [
"unicode-normalization", "unicode-normalization",
] ]
[[package]]
name = "strinto"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
]
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"
@ -3090,15 +3241,6 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "termcolor"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.51" version = "1.0.51"
@ -3214,6 +3356,20 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-util"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.3" version = "0.6.3"

View File

@ -17,6 +17,7 @@ use_barrel = ["barrel", "sqlformat"]
default = ["use_barrel"] default = ["use_barrel"]
[dependencies] [dependencies]
strinto = { path = "./strinto" }
entity = { path = "./entity" } entity = { path = "./entity" }
sea-orm = { version = "0.12.10", features = [ sea-orm = { version = "0.12.10", features = [
"runtime-tokio-native-tls", "runtime-tokio-native-tls",
@ -24,14 +25,15 @@ sea-orm = { version = "0.12.10", features = [
] } ] }
sqlformat = { version = "0.2.2", optional = true } sqlformat = { version = "0.2.2", optional = true }
anyhow = "1.0.75" anyhow = "1.0.75"
axum = "0.6.20" axum = "0.7"
barrel = { version = "0.7.0", optional = true, features = ["pg"] } barrel = { version = "0.7.0", optional = true, features = ["pg"] }
dotenvy = "0.15.7" dotenvy = "0.15.7"
mime_guess = "2.0.4" mime_guess = "2.0.4"
minijinja = { version = "1.0.8", features = [ minijinja = { version = "1.0.11", features = [
"loader", "loader",
"builtins", "builtins",
"urlencode", "urlencode",
"deserialization",
] } ] }
minijinja-autoreload = "1.0.8" minijinja-autoreload = "1.0.8"
once_cell = "1.18.0" once_cell = "1.18.0"
@ -45,6 +47,6 @@ serde = { version = "1.0.188", features = ["derive"] }
tokio = { version = "1.32.0", features = ["full"] } tokio = { version = "1.32.0", features = ["full"] }
dunce = "1.0.4" dunce = "1.0.4"
log = "0.4.20" log = "0.4.20"
env_logger = "0.10.1" env_logger = "0.11"
serde_json = "1.0.108" serde_json = "1.0.108"
slug = "0.1.5" slug = "0.1.5"

View File

@ -1,7 +1,7 @@
pub use config::AdminModelConfig; pub use config::AdminModelConfig;
pub use dto::AdminApp; pub use dto::AdminApp;
pub use dto::AdminModel; pub use dto::AdminModel;
pub use repository::{AdminRepository, RepositoryList}; pub use repository::{AdminRepository, RepositoryInfo, RepositoryList};
mod auth { mod auth {
@ -95,8 +95,33 @@ pub mod repository {
} }
} }
// each repository has to implement a repo info.
#[derive(Serialize)]
pub struct RepositoryInfo {
name: String,
lookup_key: String,
display_list: Vec<String>,
}
impl RepositoryInfo {
pub fn new(name: &str, lookup_key: &str) -> Self {
RepositoryInfo {
name: name.to_owned(),
lookup_key: lookup_key.to_owned(),
display_list: vec![],
}
}
pub fn display_list(mut self, display_list: &[&str]) -> RepositoryInfo {
self.display_list = display_list.iter().map(|&e| e.to_string()).collect();
self
}
}
pub trait AdminRepository: Send + Sync { pub trait AdminRepository: Send + Sync {
fn get_item(&self, id: usize) -> Option<Value>; fn get_item(&self, id: usize) -> Option<Value>;
fn get_repo_info(&self) -> RepositoryInfo;
fn get_list(&self) -> RepositoryList; fn get_list(&self) -> RepositoryList;
} }
} }

View File

@ -1,32 +1,49 @@
// implementation of static repository // implementation of static repository
use super::domain::{AdminModelConfig, AdminRepository, RepositoryList}; use super::domain::{AdminModelConfig, AdminRepository, RepositoryInfo, RepositoryList};
use super::state::AdminRegistry; use super::state::AdminRegistry;
use serde_json::{json, Value}; use serde_json::{json, Value};
struct MyStaticRepository {} struct MyStaticRepository {
content: Vec<Value>,
}
impl AdminRepository for MyStaticRepository { impl MyStaticRepository {
fn get_item(&self, id: usize) -> Option<Value> { pub fn new() -> Self {
Some(json!({ MyStaticRepository {
"name": "Adam", content: vec![
"age": id, json!({"id": 1, "name": "Strange", "age": 150, "level": "master" }),
})) json!({"id": 2, "name": "Adam", "age": 12, "powers": 8}),
} json!({"id": 3, "name": "Tony", "age": 42, "powers": 0}),
json!({"id": 4, "name": "Rex", "age": 72}),
fn get_list(&self) -> RepositoryList { json!({"id": 5, "name": "Justin", "age": 46}),
RepositoryList::List { json!({"id": 6, "name": "Reacher", "age": 39, "level": "adept", "powers": 0}),
values: vec![ json!({"id": 7, "name": "Arnold", "age": 64}),
json!({"name": "Strange", "age": 150 }),
json!({"name": "Adam", "age": 12}),
], ],
} }
} }
} }
impl AdminRepository for MyStaticRepository {
fn get_item(&self, id: usize) -> Option<Value> {
self.content.get(id).cloned()
}
fn get_list(&self) -> RepositoryList {
RepositoryList::List {
values: self.content.clone(),
}
}
fn get_repo_info(&self) -> RepositoryInfo {
RepositoryInfo::new("My Static Repository", "id")
.display_list(&["id", "name", "age", "level"])
}
}
pub fn register_example(registry: &mut AdminRegistry) { pub fn register_example(registry: &mut AdminRegistry) {
let app_key = registry.register_app("Example App"); let app_key = registry.register_app("Example App");
let repo = MyStaticRepository {}; let repo = MyStaticRepository::new();
let model_config = AdminModelConfig { let model_config = AdminModelConfig {
app_key: app_key, app_key: app_key,
name: "ExampleModel".to_owned(), name: "ExampleModel".to_owned(),

View File

@ -10,7 +10,7 @@ use crate::service::templates;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use super::domain::RepositoryList; use super::domain::{RepositoryInfo, RepositoryList};
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Question { pub struct Question {
@ -43,6 +43,7 @@ pub struct AdminContext {
pub request: AdminRequest, pub request: AdminRequest,
pub available_apps: Vec<AdminApp>, pub available_apps: Vec<AdminApp>,
pub item_info: Option<RepositoryInfo>,
pub item_list: RepositoryList, pub item_list: RepositoryList,
pub item: Option<Value>, pub item: Option<Value>,
} }
@ -64,6 +65,7 @@ impl Default for AdminContext {
request: AdminRequest { request: AdminRequest {
path: "".to_owned(), path: "".to_owned(),
}, },
item_info: None,
item_list: RepositoryList::Empty, item_list: RepositoryList::Empty,
item: None, item: None,
} }
@ -106,6 +108,8 @@ pub async fn list_item_collection(
// we should consider using Vec<Value> instead in get_list. // we should consider using Vec<Value> instead in get_list.
AdminContext { AdminContext {
available_apps: registry.get_apps(), available_apps: registry.get_apps(),
content: model_key.to_owned(),
item_info: Some(repo.get_repo_info()),
item_list: repo.get_list(), item_list: repo.get_list(),
..Default::default() ..Default::default()
} }

View File

@ -14,6 +14,7 @@ use log::info;
use std::env; use std::env;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use tokio::net::TcpListener;
async fn home(templates: State<templates::Templates>) -> impl IntoResponse { async fn home(templates: State<templates::Templates>) -> impl IntoResponse {
templates.render_html("index.html", ()) templates.render_html("index.html", ())
@ -70,11 +71,13 @@ async fn main() {
}; };
info!("listening on {}", listen_addr); info!("listening on {}", listen_addr);
info!("admin on: http://{}/admin", server_addr); info!("admin on: http://{}/admin", server_addr);
axum::Server::bind(&listen_addr) let listener = TcpListener::bind(&listen_addr)
.serve(app.into_make_service()) .await
.expect("Could not bind TCP Listener.");
axum::serve(listener, app.into_make_service())
.with_graceful_shutdown(shutdown_signal()) .with_graceful_shutdown(shutdown_signal())
.await .await
.unwrap(); .expect("Could not start serving Axum");
} }
async fn shutdown_signal() { async fn shutdown_signal() {

View File

@ -1,7 +1,7 @@
/* Default handlers */ /* Default handlers */
use crate::service::templates::Templates; use crate::service::templates::Templates;
use axum::{ use axum::{
body::{boxed, Full}, body::Body,
extract::State, extract::State,
http::{header, StatusCode, Uri}, http::{header, StatusCode, Uri},
response::{IntoResponse, Response}, response::{IntoResponse, Response},
@ -55,7 +55,7 @@ where
let path: &str = self.path.as_ref(); let path: &str = self.path.as_ref();
match E::get(path) { match E::get(path) {
Some(content) => { Some(content) => {
let body = boxed(Full::from(content.data)); let body = Body::from(content.data);
let mime = mime_guess::from_path(path).first_or_octet_stream(); let mime = mime_guess::from_path(path).first_or_octet_stream();
Response::builder() Response::builder()
.header(header::CONTENT_TYPE, mime.as_ref()) .header(header::CONTENT_TYPE, mime.as_ref())

View File

@ -1,8 +1,10 @@
use crate::service::error::Error; use crate::service::error::Error;
use axum::response::Html; use axum::response::Html;
use minijinja::value::ValueKind;
use minijinja::{path_loader, Environment, Value}; use minijinja::{path_loader, Environment, Value};
use minijinja_autoreload::AutoReloader; use minijinja_autoreload::AutoReloader;
use pulldown_cmark::Event; use pulldown_cmark::Event;
use serde::Deserializer;
use std::sync::Arc; use std::sync::Arc;
#[derive(Clone)] #[derive(Clone)]
@ -19,6 +21,7 @@ impl Templates {
environment.add_filter("none", none); environment.add_filter("none", none);
environment.add_filter("markdown", markdown); environment.add_filter("markdown", markdown);
environment.add_filter("yesno", filter_yesno); environment.add_filter("yesno", filter_yesno);
environment.add_filter("table_keys", collect_unique_keys);
environment.add_function("static", tpl_static); environment.add_function("static", tpl_static);
environment.add_function("url", tpl_url); environment.add_function("url", tpl_url);
environment.add_function("csrf_token", tpl_to_be_implemented); environment.add_function("csrf_token", tpl_to_be_implemented);
@ -93,3 +96,71 @@ pub fn none(value: Value, other: Option<Value>) -> Value {
value value
} }
} }
fn tpl_table(values: Vec<Value>) -> Value {
Value::from_safe_string(format!(
"Output: {}",
values.first().expect("No values found.").kind()
))
}
mod helper {
use std::collections::HashSet;
pub struct OrderedSet<T> {
set: HashSet<T>,
vec: Vec<T>,
}
impl<T: Eq + std::hash::Hash + Clone> OrderedSet<T> {
pub fn new() -> Self {
OrderedSet {
set: HashSet::new(),
vec: Vec::new(),
}
}
pub fn insert(&mut self, value: T) {
if self.set.insert(value.clone()) {
self.vec.push(value);
}
}
pub fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
for item in iter {
self.insert(item);
}
}
pub fn iter(&self) -> std::slice::Iter<T> {
self.vec.iter()
}
}
impl<T> IntoIterator for OrderedSet<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.vec.into_iter()
}
}
}
fn collect_unique_keys(values: Value) -> Value {
use helper::OrderedSet;
use serde::Deserialize;
let mut unique_keys: OrderedSet<String> = OrderedSet::new();
if let Ok(vec) = Vec::<serde_json::Value>::deserialize(values) {
for value in vec.iter() {
if let Some(dict) = value.as_object() {
let keys_vec: Vec<String> = dict.keys().map(|s| s.to_owned()).collect();
unique_keys.extend(keys_vec);
}
}
}
let keys_list = unique_keys.into_iter().collect::<Vec<_>>();
Value::from(keys_list)
}

View File

@ -10,23 +10,42 @@
{% endblock %} {% endblock %}
{% if item_list %} {% if item_list %}
{% set item_keys = item_info.display_list or item_list|table_keys %}
{% if item_info.lookup_key in item_keys %}
{% set primary_key = item_info.lookup_key %}
{% endif %}
<div class="ui short scrolling container"> <div class="ui short scrolling container">
<table class="ui very compact celled table head stuck unstackable"> <table class="ui very compact celled table head stuck unstackable">
<caption>Table Caption</caption> <caption>{{ item_info.name|none(content) }}</caption>
<thead> <thead>
<tr> <tr>
<th>Header1</th> {% for key in item_keys %}
<th>Header2</th> {% if key==primary_key %}
<th>Header3</th> <th class="blue"><i class="key icon"></i>
{{ key|capitalize }}
</th>
{% else %}
<th>{{ key|capitalize }}</th>
{% endif %}
{% endfor %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for item in item_list %} {% for item in item_list %}
<tr> <tr>
<td>{{ item.name}}</td> {% for key in item_keys %}
<td>{{ item.age }}</td> {% if key==primary_key %}
<td>{{ item }}</td> <td class="selectable warning">
<a href="#">{{ item[key] }}</a>
</td>
{% else %}
<td>{{ item[key] }}</td>
{% endif %}
{% endfor %}
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -34,13 +53,6 @@
</tfoot> </tfoot>
</table> </table>
</div> </div>
{% set x="name" %}
<li>{{ item[x] }}</li>
</ul>
{% else %} {% else %}
No Items found. No Items found.
{% endif %} {% endif %}

View File

@ -24,6 +24,9 @@
{% include "fomantic.html" %} {% include "fomantic.html" %}
{% endblock %} {% endblock %}
{% block extrahead %}
{% endblock extrahead %}
</head> </head>
<body> <body>

View File

@ -1,4 +1,18 @@
{% extends "base.html.j2" %} {% extends "base.jinja" %}
{% block extrahead %}
<style type="text/css">
.hidden.menu {
display: none;
}
.masthead.segment {
min-height: 700px;
padding: 1em 0em;
}
</style>
{% endblock extrahead %}
{% block content %} {% block content %}
<div class="pusher"> <div class="pusher">