code: rudimentary implementation of detail and create
This commit is contained in:
		
							parent
							
								
									d1c98516a8
								
							
						
					
					
						commit
						c439220409
					
				
							
								
								
									
										1
									
								
								TODOS.md
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								TODOS.md
									
									
									
									
									
								
							@ -9,3 +9,4 @@
 | 
				
			|||||||
 - [ ] better 404 handling
 | 
					 - [ ] better 404 handling
 | 
				
			||||||
 - [ ] better 500 handling
 | 
					 - [ ] better 500 handling
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,8 @@ pub use config::AdminModelConfig;
 | 
				
			|||||||
pub use dto::AdminApp;
 | 
					pub use dto::AdminApp;
 | 
				
			||||||
pub use dto::AdminModel;
 | 
					pub use dto::AdminModel;
 | 
				
			||||||
pub use repository::{
 | 
					pub use repository::{
 | 
				
			||||||
    AdminRepository, RepoInfo, RepositoryInfo, RepositoryInfoBuilder, RepositoryList,
 | 
					    AdminRepository, LookupKey, RepoInfo, RepositoryInfo, RepositoryInfoBuilder, RepositoryItem,
 | 
				
			||||||
 | 
					    RepositoryList,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod auth {
 | 
					mod auth {
 | 
				
			||||||
@ -49,29 +50,77 @@ mod dto {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod repository {
 | 
					pub mod repository {
 | 
				
			||||||
 | 
					    use super::dto::AdminModel;
 | 
				
			||||||
    use derive_builder::Builder;
 | 
					    use derive_builder::Builder;
 | 
				
			||||||
    use serde::{Serialize, Serializer};
 | 
					    use serde::{Serialize, Serializer};
 | 
				
			||||||
    use serde_json::Value;
 | 
					    use serde_json::Value;
 | 
				
			||||||
    use std::vec::IntoIter;
 | 
					    use std::vec::IntoIter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub enum LookupKey {
 | 
				
			||||||
 | 
					        Integer(usize),
 | 
				
			||||||
 | 
					        String(String),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Note that LookupKey auto converts to integer.
 | 
				
			||||||
 | 
					    impl From<String> for LookupKey {
 | 
				
			||||||
 | 
					        fn from(s: String) -> Self {
 | 
				
			||||||
 | 
					            if let Ok(int_key) = s.parse::<usize>() {
 | 
				
			||||||
 | 
					                LookupKey::Integer(int_key)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                LookupKey::String(s)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<&str> for LookupKey {
 | 
				
			||||||
 | 
					        fn from(s: &str) -> Self {
 | 
				
			||||||
 | 
					            if let Ok(int_key) = s.parse::<usize>() {
 | 
				
			||||||
 | 
					                LookupKey::Integer(int_key)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                LookupKey::String(s.to_owned())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<usize> for LookupKey {
 | 
				
			||||||
 | 
					        fn from(i: usize) -> Self {
 | 
				
			||||||
 | 
					            LookupKey::Integer(i)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl std::fmt::Display for LookupKey {
 | 
				
			||||||
 | 
					        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
 | 
					            match self {
 | 
				
			||||||
 | 
					                LookupKey::Integer(i) => write!(f, "{}", i),
 | 
				
			||||||
 | 
					                LookupKey::String(s) => write!(f, "{}", s),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Serialize)]
 | 
				
			||||||
 | 
					    pub struct RepositoryItem {
 | 
				
			||||||
 | 
					        pub fields: Value,
 | 
				
			||||||
 | 
					        pub detail_url: Option<String>,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub enum RepositoryList {
 | 
					    pub enum RepositoryList {
 | 
				
			||||||
        Empty,
 | 
					        Empty,
 | 
				
			||||||
        List {
 | 
					        List {
 | 
				
			||||||
            values: Vec<Value>,
 | 
					            values: Vec<RepositoryItem>,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        Page {
 | 
					        Page {
 | 
				
			||||||
            values: Vec<Value>,
 | 
					            values: Vec<RepositoryItem>,
 | 
				
			||||||
            offset: usize,
 | 
					            offset: usize,
 | 
				
			||||||
            total: usize,
 | 
					            total: usize,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        Stream {
 | 
					        Stream {
 | 
				
			||||||
            values: Vec<Value>,
 | 
					            values: Vec<RepositoryItem>,
 | 
				
			||||||
            next_index: Option<String>,
 | 
					            next_index: Option<String>,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    impl IntoIterator for RepositoryList {
 | 
					    impl IntoIterator for RepositoryList {
 | 
				
			||||||
        type Item = Value;
 | 
					        type Item = RepositoryItem;
 | 
				
			||||||
        type IntoIter = IntoIter<Self::Item>;
 | 
					        type IntoIter = IntoIter<Self::Item>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fn into_iter(self) -> Self::IntoIter {
 | 
					        fn into_iter(self) -> Self::IntoIter {
 | 
				
			||||||
@ -148,11 +197,11 @@ pub mod repository {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub trait AdminRepository: Send + Sync {
 | 
					    pub trait AdminRepository: Send + Sync {
 | 
				
			||||||
        fn info(&self) -> RepositoryInfo;
 | 
					        fn info(&self, model: &AdminModel) -> RepositoryInfo;
 | 
				
			||||||
        fn list(&self) -> RepositoryList;
 | 
					        fn list(&self, model: &AdminModel) -> RepositoryList;
 | 
				
			||||||
        fn get(&self, id: usize) -> Option<Value>;
 | 
					        fn get(&self, model: &AdminModel, id: LookupKey) -> Option<RepositoryItem>;
 | 
				
			||||||
        fn create(&self, data: Value) -> Option<Value>;
 | 
					        fn create(&self, model: &AdminModel, data: Value) -> Option<Value>;
 | 
				
			||||||
        fn update(&self, id: usize, data: Value) -> Option<Value>;
 | 
					        fn update(&self, model: &AdminModel, id: LookupKey, data: Value) -> Option<Value>;
 | 
				
			||||||
        fn delete(&self, id: usize) -> Option<Value>;
 | 
					        fn delete(&self, model: &AdminModel, id: LookupKey) -> Option<Value>;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,8 @@
 | 
				
			|||||||
// implementation of static repository
 | 
					// implementation of static repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::domain::{AdminModelConfig, AdminRepository, RepoInfo, RepositoryInfo, RepositoryList};
 | 
					use super::domain::*;
 | 
				
			||||||
use super::state::AdminRegistry;
 | 
					use super::state::AdminRegistry;
 | 
				
			||||||
 | 
					use log::warn;
 | 
				
			||||||
use serde_json::{json, Value};
 | 
					use serde_json::{json, Value};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct MyStaticRepository {
 | 
					struct MyStaticRepository {
 | 
				
			||||||
@ -18,24 +19,54 @@ impl MyStaticRepository {
 | 
				
			|||||||
                json!({"id": 4, "name": "Rex", "age": 72}),
 | 
					                json!({"id": 4, "name": "Rex", "age": 72}),
 | 
				
			||||||
                json!({"id": 5, "name": "Justin", "age": 46}),
 | 
					                json!({"id": 5, "name": "Justin", "age": 46}),
 | 
				
			||||||
                json!({"id": 6, "name": "Reacher", "age": 39, "level": "adept", "powers": 0}),
 | 
					                json!({"id": 6, "name": "Reacher", "age": 39, "level": "adept", "powers": 0}),
 | 
				
			||||||
                json!({"id": 7, "name": "Arnold", "age": 64}),
 | 
					                json!({"id": 7, "name": "Arnold", "age": 7}),
 | 
				
			||||||
 | 
					                json!({"id": 8, "name": "Eight", "age": 8}),
 | 
				
			||||||
 | 
					                json!({"id": 9, "name": "Nine", "age": 9}),
 | 
				
			||||||
 | 
					                json!({"id": 10, "name": "Ten", "age": 10}),
 | 
				
			||||||
 | 
					                json!({"id": 11, "name": "Eleven", "age": 11}),
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl AdminRepository for MyStaticRepository {
 | 
					impl AdminRepository for MyStaticRepository {
 | 
				
			||||||
    fn get(&self, id: usize) -> Option<Value> {
 | 
					    fn get(&self, model: &AdminModel, id: LookupKey) -> Option<RepositoryItem> {
 | 
				
			||||||
        self.content.get(id).cloned()
 | 
					        if let LookupKey::Integer(id) = id {
 | 
				
			||||||
 | 
					            let item = self.content.get(id - 1).cloned().unwrap();
 | 
				
			||||||
 | 
					            Some(RepositoryItem {
 | 
				
			||||||
 | 
					                detail_url: Some(format!(
 | 
				
			||||||
 | 
					                    "{}/detail/{}",
 | 
				
			||||||
 | 
					                    model.admin_url,
 | 
				
			||||||
 | 
					                    item.get("id").unwrap()
 | 
				
			||||||
 | 
					                )),
 | 
				
			||||||
 | 
					                fields: item,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            warn!("Got non-integer lookup key: {}", id);
 | 
				
			||||||
 | 
					            None
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn list(&self) -> RepositoryList {
 | 
					    fn list(&self, model: &AdminModel) -> RepositoryList {
 | 
				
			||||||
 | 
					        // Admin needs to inject info in these.
 | 
				
			||||||
        RepositoryList::List {
 | 
					        RepositoryList::List {
 | 
				
			||||||
            values: self.content.clone(),
 | 
					            values: self
 | 
				
			||||||
 | 
					                .content
 | 
				
			||||||
 | 
					                .clone()
 | 
				
			||||||
 | 
					                .into_iter()
 | 
				
			||||||
 | 
					                .map(|item| RepositoryItem {
 | 
				
			||||||
 | 
					                    detail_url: Some(format!(
 | 
				
			||||||
 | 
					                        "{}/detail/{}",
 | 
				
			||||||
 | 
					                        model.admin_url,
 | 
				
			||||||
 | 
					                        item.get("id").unwrap()
 | 
				
			||||||
 | 
					                    )),
 | 
				
			||||||
 | 
					                    fields: item,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .collect(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn info(&self) -> RepositoryInfo {
 | 
					    fn info(&self, model: &AdminModel) -> RepositoryInfo {
 | 
				
			||||||
        RepoInfo {
 | 
					        RepoInfo {
 | 
				
			||||||
            name: "My Static Repository",
 | 
					            name: "My Static Repository",
 | 
				
			||||||
            lookup_key: "id",
 | 
					            lookup_key: "id",
 | 
				
			||||||
@ -44,17 +75,17 @@ impl AdminRepository for MyStaticRepository {
 | 
				
			|||||||
        .into()
 | 
					        .into()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn create(&self, data: Value) -> Option<Value> {
 | 
					    fn create(&self, model: &AdminModel, data: Value) -> Option<Value> {
 | 
				
			||||||
        println!("I would now create: {}", data);
 | 
					        println!("I would now create: {}", data);
 | 
				
			||||||
        None
 | 
					        None
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn update(&self, id: usize, data: Value) -> Option<Value> {
 | 
					    fn update(&self, model: &AdminModel, id: LookupKey, data: Value) -> Option<Value> {
 | 
				
			||||||
        println!("I would now update: {}, {}", id, data);
 | 
					        println!("I would now update: {}, {}", id, data);
 | 
				
			||||||
        None
 | 
					        None
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn delete(&self, id: usize) -> Option<Value> {
 | 
					    fn delete(&self, model: &AdminModel, id: LookupKey) -> Option<Value> {
 | 
				
			||||||
        println!("Would delete: {}", id);
 | 
					        println!("Would delete: {}", id);
 | 
				
			||||||
        None
 | 
					        None
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ pub fn routes() -> Router<AppState> {
 | 
				
			|||||||
        .route("/", get(views::index).post(views::index_action))
 | 
					        .route("/", get(views::index).post(views::index_action))
 | 
				
			||||||
        .route("/app/:app", get(views::list_app))
 | 
					        .route("/app/:app", get(views::list_app))
 | 
				
			||||||
        .route("/app/:app/model/:model", get(views::list_item_collection))
 | 
					        .route("/app/:app/model/:model", get(views::list_item_collection))
 | 
				
			||||||
 | 
					        .route("/app/:app/model/:model/add", get(views::create_item))
 | 
				
			||||||
        .route(
 | 
					        .route(
 | 
				
			||||||
            "/app/:app/model/:model/detail/:id",
 | 
					            "/app/:app/model/:model/detail/:id",
 | 
				
			||||||
            get(views::item_details),
 | 
					            get(views::item_details),
 | 
				
			||||||
 | 
				
			|||||||
@ -56,15 +56,16 @@ impl AdminRegistry {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn model_from_internal(&self, internal_model: &internal::AdminModel) -> AdminModel {
 | 
					    fn model_from_internal(&self, internal_model: &internal::AdminModel) -> AdminModel {
 | 
				
			||||||
 | 
					        let admin_url = format!(
 | 
				
			||||||
 | 
					            "/{}/app/{}/model/{}",
 | 
				
			||||||
 | 
					            self.base_path, internal_model.app_key, internal_model.model_key
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        AdminModel {
 | 
					        AdminModel {
 | 
				
			||||||
            key: internal_model.model_key.clone(),
 | 
					            key: internal_model.model_key.clone(),
 | 
				
			||||||
            name: internal_model.name.clone(),
 | 
					            name: internal_model.name.clone(),
 | 
				
			||||||
            admin_url: format!(
 | 
					 | 
				
			||||||
                "/{}/app/{}/model/{}",
 | 
					 | 
				
			||||||
                self.base_path, internal_model.app_key, internal_model.model_key
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            view_only: false,
 | 
					            view_only: false,
 | 
				
			||||||
            add_url: None,
 | 
					            add_url: Some(format!("{}/add", admin_url)),
 | 
				
			||||||
 | 
					            admin_url: admin_url,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -93,9 +94,9 @@ impl AdminRegistry {
 | 
				
			|||||||
        if self.models.contains_key(&local_config_name) {
 | 
					        if self.models.contains_key(&local_config_name) {
 | 
				
			||||||
            return Err(format!("Model {} already exists", local_config_name));
 | 
					            return Err(format!("Model {} already exists", local_config_name));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let model_key = local_config.model_key.clone();
 | 
					        let full_model_key = local_config_name.clone();
 | 
				
			||||||
        self.models.insert(local_config_name, local_config);
 | 
					        self.models.insert(local_config_name, local_config);
 | 
				
			||||||
        Ok(model_key)
 | 
					        Ok(full_model_key)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn register_model<R: AdminRepository + 'static>(
 | 
					    pub fn register_model<R: AdminRepository + 'static>(
 | 
				
			||||||
@ -108,8 +109,13 @@ impl AdminRegistry {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn get_repository(&self, model_key: &str) -> Result<&Box<dyn AdminRepository>, String> {
 | 
					    pub fn get_repository(
 | 
				
			||||||
        if let Some(repo) = self.repositories.get(model_key) {
 | 
					        &self,
 | 
				
			||||||
 | 
					        app_key: &str,
 | 
				
			||||||
 | 
					        model_key: &str,
 | 
				
			||||||
 | 
					    ) -> Result<&Box<dyn AdminRepository>, String> {
 | 
				
			||||||
 | 
					        let full_model_key = format!("{}.{}", app_key, model_key);
 | 
				
			||||||
 | 
					        if let Some(repo) = self.repositories.get(&full_model_key) {
 | 
				
			||||||
            return Ok(repo);
 | 
					            return Ok(repo);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            return Err("Couldn't find repository".to_owned());
 | 
					            return Err("Couldn't find repository".to_owned());
 | 
				
			||||||
@ -143,4 +149,14 @@ mod internal {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<(&str, &str)> for AdminModel {
 | 
				
			||||||
 | 
					        fn from(value: (&str, &str)) -> Self {
 | 
				
			||||||
 | 
					            AdminModel {
 | 
				
			||||||
 | 
					                app_key: value.0.to_owned(),
 | 
				
			||||||
 | 
					                model_key: slug::slugify(value.1.to_owned()),
 | 
				
			||||||
 | 
					                name: value.1.to_owned(),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,27 +1,15 @@
 | 
				
			|||||||
use std::sync::Arc;
 | 
					use std::sync::Arc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use axum::extract::Path;
 | 
					use axum::extract::Path;
 | 
				
			||||||
use axum::{extract::State, response::IntoResponse, Form};
 | 
					use axum::{extract::State, response::IntoResponse};
 | 
				
			||||||
use log::info;
 | 
					use log::info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::admin::domain::{AdminApp, AdminModel};
 | 
					use crate::admin::domain::{AdminApp, AdminModel};
 | 
				
			||||||
use crate::admin::state;
 | 
					use crate::admin::state;
 | 
				
			||||||
use crate::service::templates;
 | 
					use crate::service::templates;
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use serde_json::Value;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::domain::{RepositoryInfo, RepositoryList};
 | 
					use super::domain::{LookupKey, RepositoryInfo, RepositoryItem, RepositoryList};
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Deserialize)]
 | 
					 | 
				
			||||||
pub struct Question {
 | 
					 | 
				
			||||||
    question: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Deserialize)]
 | 
					 | 
				
			||||||
pub struct ExampleData {
 | 
					 | 
				
			||||||
    title: String,
 | 
					 | 
				
			||||||
    content: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize)]
 | 
					#[derive(Serialize, Deserialize)]
 | 
				
			||||||
pub struct AdminRequest {
 | 
					pub struct AdminRequest {
 | 
				
			||||||
@ -43,9 +31,10 @@ pub struct AdminContext {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    pub request: AdminRequest,
 | 
					    pub request: AdminRequest,
 | 
				
			||||||
    pub available_apps: Vec<AdminApp>,
 | 
					    pub available_apps: Vec<AdminApp>,
 | 
				
			||||||
 | 
					    pub item_model: Option<AdminModel>,
 | 
				
			||||||
    pub item_info: Option<RepositoryInfo>,
 | 
					    pub item_info: Option<RepositoryInfo>,
 | 
				
			||||||
    pub item_list: RepositoryList,
 | 
					    pub item_list: RepositoryList,
 | 
				
			||||||
    pub item: Option<Value>,
 | 
					    pub item: Option<RepositoryItem>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for AdminContext {
 | 
					impl Default for AdminContext {
 | 
				
			||||||
@ -65,6 +54,7 @@ impl Default for AdminContext {
 | 
				
			|||||||
            request: AdminRequest {
 | 
					            request: AdminRequest {
 | 
				
			||||||
                path: "".to_owned(),
 | 
					                path: "".to_owned(),
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
					            item_model: None,
 | 
				
			||||||
            item_info: None,
 | 
					            item_info: None,
 | 
				
			||||||
            item_list: RepositoryList::Empty,
 | 
					            item_list: RepositoryList::Empty,
 | 
				
			||||||
            item: None,
 | 
					            item: None,
 | 
				
			||||||
@ -86,7 +76,7 @@ pub async fn index(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Index Action is POST to the index site. We can anchor some general business code here.
 | 
					// Index Action is POST to the index site. We can anchor some general business code here.
 | 
				
			||||||
pub async fn index_action(Form(example_data): Form<ExampleData>) -> impl IntoResponse {
 | 
					pub async fn index_action() -> impl IntoResponse {
 | 
				
			||||||
    "There is your answer!".to_owned()
 | 
					    "There is your answer!".to_owned()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -104,13 +94,18 @@ pub async fn list_item_collection(
 | 
				
			|||||||
    Path((app_key, model_key)): Path<(String, String)>,
 | 
					    Path((app_key, model_key)): Path<(String, String)>,
 | 
				
			||||||
) -> impl IntoResponse {
 | 
					) -> impl IntoResponse {
 | 
				
			||||||
    info!("list_item_collection {} for model {}", app_key, model_key);
 | 
					    info!("list_item_collection {} for model {}", app_key, model_key);
 | 
				
			||||||
    let context = if let Ok(repo) = registry.get_repository(&model_key) {
 | 
					    let context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) {
 | 
				
			||||||
        // we should consider using Vec<Value> instead in get_list.
 | 
					        let admin_model = registry
 | 
				
			||||||
 | 
					            .get_model(&app_key, &model_key)
 | 
				
			||||||
 | 
					            .expect("Admin Model not found?"); // TODO: do we really need to differentiate between AdminModel and Repository, if both need to co-exist
 | 
				
			||||||
 | 
					                                               // Note: AdminModel contains Registry Data, while Repository only contains user data; however, both could be retrieved more reliably from each other.
 | 
				
			||||||
 | 
					                                               // Another solution would be a clear "AdminRepositoryContext", that contains information about the current model.
 | 
				
			||||||
        AdminContext {
 | 
					        AdminContext {
 | 
				
			||||||
            available_apps: registry.get_apps(),
 | 
					            available_apps: registry.get_apps(),
 | 
				
			||||||
            content: model_key.to_owned(),
 | 
					            content: model_key.to_owned(),
 | 
				
			||||||
            item_info: Some(repo.info()),
 | 
					            item_info: Some(repo.info(&admin_model)),
 | 
				
			||||||
            item_list: repo.list(),
 | 
					            item_list: repo.list(&admin_model),
 | 
				
			||||||
 | 
					            item_model: Some(admin_model),
 | 
				
			||||||
            ..Default::default()
 | 
					            ..Default::default()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
@ -124,7 +119,6 @@ pub async fn list_item_collection(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Items Action is a POST to an item list. By default these are actions, that work on a list of items as input.
 | 
					// Items Action is a POST to an item list. By default these are actions, that work on a list of items as input.
 | 
				
			||||||
pub async fn item_collection_action(
 | 
					pub async fn item_collection_action(
 | 
				
			||||||
    Form(question): Form<Question>,
 | 
					 | 
				
			||||||
    Path((app_key, model_key)): Path<(String, String)>,
 | 
					    Path((app_key, model_key)): Path<(String, String)>,
 | 
				
			||||||
) -> impl IntoResponse {
 | 
					) -> impl IntoResponse {
 | 
				
			||||||
    "There is your answer!".to_owned()
 | 
					    "There is your answer!".to_owned()
 | 
				
			||||||
@ -133,14 +127,62 @@ pub async fn item_collection_action(
 | 
				
			|||||||
// Item Details shows one single dataset.
 | 
					// Item Details shows one single dataset.
 | 
				
			||||||
pub async fn item_details(
 | 
					pub async fn item_details(
 | 
				
			||||||
    templates: State<templates::Templates>,
 | 
					    templates: State<templates::Templates>,
 | 
				
			||||||
 | 
					    registry: State<Arc<state::AdminRegistry>>,
 | 
				
			||||||
    Path((app_key, model_key, id)): Path<(String, String, String)>,
 | 
					    Path((app_key, model_key, id)): Path<(String, String, String)>,
 | 
				
			||||||
) -> impl IntoResponse {
 | 
					) -> impl IntoResponse {
 | 
				
			||||||
    templates.render_html("admin/items/item_detail.jinja", ())
 | 
					    let context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) {
 | 
				
			||||||
 | 
					        let admin_model = registry
 | 
				
			||||||
 | 
					            .get_model(&app_key, &model_key)
 | 
				
			||||||
 | 
					            .expect("Admin Model not found?"); // TODO: do we really need to differentiate between AdminModel and Repository, if both need to co-exist
 | 
				
			||||||
 | 
					                                               // Note: AdminModel contains Registry Data, while Repository only contains user data; however, both could be retrieved more reliably from each other.
 | 
				
			||||||
 | 
					                                               // Another solution would be a clear "AdminRepositoryContext", that contains information about the current model.
 | 
				
			||||||
 | 
					        let key: LookupKey = id.into();
 | 
				
			||||||
 | 
					        AdminContext {
 | 
				
			||||||
 | 
					            available_apps: registry.get_apps(),
 | 
				
			||||||
 | 
					            content: model_key.to_owned(),
 | 
				
			||||||
 | 
					            item_info: Some(repo.info(&admin_model)),
 | 
				
			||||||
 | 
					            item_list: repo.list(&admin_model),
 | 
				
			||||||
 | 
					            item: repo.get(&admin_model, key),
 | 
				
			||||||
 | 
					            item_model: Some(admin_model),
 | 
				
			||||||
 | 
					            ..Default::default()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        AdminContext {
 | 
				
			||||||
 | 
					            available_apps: registry.get_apps(),
 | 
				
			||||||
 | 
					            ..Default::default()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    templates.render_html("admin/items/item_detail.jinja", context)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn create_item(
 | 
				
			||||||
 | 
					    templates: State<templates::Templates>,
 | 
				
			||||||
 | 
					    registry: State<Arc<state::AdminRegistry>>,
 | 
				
			||||||
 | 
					    Path((app_key, model_key)): Path<(String, String)>,
 | 
				
			||||||
 | 
					) -> impl IntoResponse {
 | 
				
			||||||
 | 
					    let context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) {
 | 
				
			||||||
 | 
					        let admin_model = registry
 | 
				
			||||||
 | 
					            .get_model(&app_key, &model_key)
 | 
				
			||||||
 | 
					            .expect("Admin Model not found?");
 | 
				
			||||||
 | 
					        AdminContext {
 | 
				
			||||||
 | 
					            available_apps: registry.get_apps(),
 | 
				
			||||||
 | 
					            content: model_key.to_owned(),
 | 
				
			||||||
 | 
					            item_info: Some(repo.info(&admin_model)),
 | 
				
			||||||
 | 
					            item_list: repo.list(&admin_model),
 | 
				
			||||||
 | 
					            item_model: Some(admin_model),
 | 
				
			||||||
 | 
					            ..Default::default()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        AdminContext {
 | 
				
			||||||
 | 
					            available_apps: registry.get_apps(),
 | 
				
			||||||
 | 
					            ..Default::default()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    templates.render_html("admin/items/item_create.jinja", context)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Item Action allows running an action on one single dataset.
 | 
					// Item Action allows running an action on one single dataset.
 | 
				
			||||||
pub async fn item_action(
 | 
					pub async fn item_action(
 | 
				
			||||||
    Form(question): Form<Question>,
 | 
					 | 
				
			||||||
    Path((app_key, model_key, model_id)): Path<(String, String, String)>,
 | 
					    Path((app_key, model_key, model_id)): Path<(String, String, String)>,
 | 
				
			||||||
) -> impl IntoResponse {
 | 
					) -> impl IntoResponse {
 | 
				
			||||||
    "There is your answer!".to_owned()
 | 
					    "There is your answer!".to_owned()
 | 
				
			||||||
 | 
				
			|||||||
@ -20,11 +20,13 @@
 | 
				
			|||||||
    margin-left: var(--sidebar-margin);
 | 
					    margin-left: var(--sidebar-margin);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
@media screen and (min-width: 1920px) {
 | 
					@media screen and (min-width: 1920px) {
 | 
				
			||||||
    .pushover {
 | 
					    .pushover {
 | 
				
			||||||
        margin-left: 0px;
 | 
					        margin-left: 0px;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#main_sidemenu {
 | 
					#main_sidemenu {
 | 
				
			||||||
    background-color: rgb(27, 28, 29);
 | 
					    background-color: rgb(27, 28, 29);
 | 
				
			||||||
 | 
				
			|||||||
@ -110,12 +110,12 @@
 | 
				
			|||||||
            <a href="{{ model.admin_url }}" {% if model.admin_url in request.path|urlencode %} aria-current="page" {%
 | 
					            <a href="{{ model.admin_url }}" {% if model.admin_url in request.path|urlencode %} aria-current="page" {%
 | 
				
			||||||
              endif %}>{{ model.name }}</a>
 | 
					              endif %}>{{ model.name }}</a>
 | 
				
			||||||
            {% else %}
 | 
					            {% else %}
 | 
				
			||||||
            {{ model.name }}
 | 
					            <span>{{ model.name }}</span>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <i class="plus link icon"></i>
 | 
					
 | 
				
			||||||
            {% if model.add_url %}
 | 
					            {% if model.add_url %}<i class="plus link icon">
 | 
				
			||||||
            <a href="{{ model.add_url }}" class="addlink">{{ translate( 'Add') }}</a>
 | 
					              <a href="{{ model.add_url }}" class="addlink">{{ translate( 'Add') }}</a></i>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
            {% endfor %}
 | 
					            {% endfor %}
 | 
				
			||||||
            </table>
 | 
					            </table>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										17
									
								
								templates/admin/items/item_create.jinja
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								templates/admin/items/item_create.jinja
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					{% extends "admin/base.jinja" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% macro input(name, value="", type="text") -%}
 | 
				
			||||||
 | 
					<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
 | 
				
			||||||
 | 
					{%- endmacro %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					Create {{item_model.name}} in {{item_info.name}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<form>
 | 
				
			||||||
 | 
					    {% set fields = item_info.display_list %}
 | 
				
			||||||
 | 
					    {% for field in fields %}
 | 
				
			||||||
 | 
					    <p><label>{{field}}</label>{{ input(field) }}</p>
 | 
				
			||||||
 | 
					    {% endfor %}
 | 
				
			||||||
 | 
					    <input type="submit" name="submit" value="Create">
 | 
				
			||||||
 | 
					</form>
 | 
				
			||||||
 | 
					{% endblock content %}
 | 
				
			||||||
@ -1 +1,11 @@
 | 
				
			|||||||
Item Detail.
 | 
					{% extends "admin/base.jinja" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% if item %}
 | 
				
			||||||
 | 
					{{item.fields}}
 | 
				
			||||||
 | 
					{% else %}
 | 
				
			||||||
 | 
					No Item found.
 | 
				
			||||||
 | 
					{% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% endblock content %}
 | 
				
			||||||
@ -16,7 +16,7 @@
 | 
				
			|||||||
{% set primary_key = item_info.lookup_key %}
 | 
					{% set primary_key = item_info.lookup_key %}
 | 
				
			||||||
{% endif %}
 | 
					{% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="ui short scrolling container">
 | 
					<div class="ui basic container">
 | 
				
			||||||
  <table class="ui very compact celled table head stuck unstackable">
 | 
					  <table class="ui very compact celled table head stuck unstackable">
 | 
				
			||||||
    <caption>{{ item_info.name|none(content) }}</caption>
 | 
					    <caption>{{ item_info.name|none(content) }}</caption>
 | 
				
			||||||
    <thead>
 | 
					    <thead>
 | 
				
			||||||
@ -39,12 +39,11 @@
 | 
				
			|||||||
      <tr>
 | 
					      <tr>
 | 
				
			||||||
        {% for key in item_keys %}
 | 
					        {% for key in item_keys %}
 | 
				
			||||||
        {% if key==primary_key %}
 | 
					        {% if key==primary_key %}
 | 
				
			||||||
        <td class="selectable warning">
 | 
					        <td class="selectable warning">{% if item.detail_url %}<a href="{{item.detail_url}}">{{
 | 
				
			||||||
          <a href="#">{{ item[key] }}</a>
 | 
					            item.fields[key] }}</a>{%
 | 
				
			||||||
        </td>
 | 
					          else %}{{item.fields[key] }}{% endif %}</td>
 | 
				
			||||||
        {% else %}
 | 
					        {% else %}
 | 
				
			||||||
        <td>{{ item[key] }}</td>
 | 
					        <td>{{ item.fields[key] }}</td>{% endif %}
 | 
				
			||||||
        {% endif %}
 | 
					 | 
				
			||||||
        {% endfor %}
 | 
					        {% endfor %}
 | 
				
			||||||
      </tr>
 | 
					      </tr>
 | 
				
			||||||
      {% endfor %}
 | 
					      {% endfor %}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user