diff --git a/TODOS.md b/TODOS.md index 462e4c9..8f7eaa7 100644 --- a/TODOS.md +++ b/TODOS.md @@ -10,3 +10,23 @@ - [ ] better 500 handling +edit functionality: + - edit-list (which fields can we edit) + -> unlike in django, we have no access to the model completely, however, we also do not have to care. + - widgets (how fields are rendered in the edit form) + -> how would i create widgets? programmatically? in templates? + -> maybe even both approaches available? + -> need a list of MVP widgets, and a way to extend them. + - validation. + -> client side as well? + -> existing solutions? + -> transparent way to display error messages at the appropriate field (probably adding results into responses) + -> required + -> invalid + -> invalid(max-length) + +auth: + - user model. + - auth middleware. + - message the user. + -> messaging system diff --git a/src/admin/domain.rs b/src/admin/domain.rs index ae4d23b..33c7bd8 100644 --- a/src/admin/domain.rs +++ b/src/admin/domain.rs @@ -200,8 +200,8 @@ pub mod repository { fn info(&self, model: &AdminModel) -> RepositoryInfo; fn list(&self, model: &AdminModel) -> RepositoryList; fn get(&self, model: &AdminModel, id: LookupKey) -> Option; - fn create(&self, model: &AdminModel, data: Value) -> Option; - fn update(&self, model: &AdminModel, id: LookupKey, data: Value) -> Option; + fn create(&self, model: &AdminModel, data: Value) -> Option; + fn update(&self, model: &AdminModel, id: LookupKey, data: Value) -> Option; fn delete(&self, model: &AdminModel, id: LookupKey) -> Option; } } diff --git a/src/admin/example.rs b/src/admin/example.rs index 14fca81..202ca24 100644 --- a/src/admin/example.rs +++ b/src/admin/example.rs @@ -75,12 +75,12 @@ impl AdminRepository for MyStaticRepository { .into() } - fn create(&self, model: &AdminModel, data: Value) -> Option { + fn create(&self, model: &AdminModel, data: Value) -> Option { println!("I would now create: {}", data); None } - fn update(&self, model: &AdminModel, id: LookupKey, data: Value) -> Option { + fn update(&self, model: &AdminModel, id: LookupKey, data: Value) -> Option { println!("I would now update: {}, {}", id, data); None } diff --git a/src/admin/mod.rs b/src/admin/mod.rs index 572e531..42a08d3 100644 --- a/src/admin/mod.rs +++ b/src/admin/mod.rs @@ -1,4 +1,4 @@ -use axum::{routing::get, Router}; +use axum::{routing::get, routing::post, Router}; use crate::state::AppState; @@ -12,7 +12,10 @@ pub fn routes() -> Router { .route("/", get(views::index).post(views::index_action)) .route("/app/:app", get(views::list_app)) .route("/app/:app/model/:model", get(views::list_item_collection)) - .route("/app/:app/model/:model/add", get(views::create_item)) + .route( + "/app/:app/model/:model/add", + get(views::new_item).post(views::create_item), + ) .route( "/app/:app/model/:model/detail/:id", get(views::item_details), diff --git a/src/admin/views.rs b/src/admin/views.rs index f4a4d2c..efb372f 100644 --- a/src/admin/views.rs +++ b/src/admin/views.rs @@ -1,8 +1,11 @@ use std::sync::Arc; use axum::extract::Path; +use axum::http::HeaderMap; +use axum::Form; use axum::{extract::State, response::IntoResponse}; use log::info; +use serde_json::Value; use crate::admin::domain::{AdminApp, AdminModel}; use crate::admin::state; @@ -18,6 +21,7 @@ pub struct AdminRequest { #[derive(Serialize)] pub struct AdminContext { + pub base: Option, pub language_code: Option, pub language_bidi: Option, pub user: Option, // Todo: user type @@ -40,6 +44,7 @@ pub struct AdminContext { impl Default for AdminContext { fn default() -> Self { AdminContext { + base: None, language_code: Some("en-us".to_string()), // Default language code language_bidi: Some(false), // Default language bidi user: None, //UserType::default(), // Assuming UserType has a Default impl @@ -62,13 +67,25 @@ impl Default for AdminContext { } } +pub fn base_template(headers: &HeaderMap) -> Option { + let hx_request = headers.get("HX-Request").is_some(); + if hx_request { + println!("HX."); + Some("admin/base_hx.jinja".to_string()) + } else { + None + } +} + pub async fn index( templates: State, registry: State>, + headers: HeaderMap, ) -> impl IntoResponse { templates.render_html( "admin/index.html", AdminContext { + base: base_template(&headers), available_apps: registry.get_apps(), ..Default::default() }, @@ -91,9 +108,11 @@ pub async fn list_app( pub async fn list_item_collection( templates: State, registry: State>, + headers: HeaderMap, Path((app_key, model_key)): Path<(String, String)>, ) -> impl IntoResponse { info!("list_item_collection {} for model {}", app_key, model_key); + let context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) { let admin_model = registry .get_model(&app_key, &model_key) @@ -101,8 +120,8 @@ pub async fn list_item_collection( // 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 { + base: base_template(&headers), 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), @@ -110,6 +129,7 @@ pub async fn list_item_collection( } } else { AdminContext { + base: base_template(&headers), available_apps: registry.get_apps(), ..Default::default() } @@ -128,6 +148,7 @@ pub async fn item_collection_action( pub async fn item_details( templates: State, registry: State>, + headers: HeaderMap, Path((app_key, model_key, id)): Path<(String, String, String)>, ) -> impl IntoResponse { let context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) { @@ -138,8 +159,8 @@ pub async fn item_details( // Another solution would be a clear "AdminRepositoryContext", that contains information about the current model. let key: LookupKey = id.into(); AdminContext { + base: base_template(&headers), 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), @@ -148,6 +169,7 @@ pub async fn item_details( } } else { AdminContext { + base: base_template(&headers), available_apps: registry.get_apps(), ..Default::default() } @@ -155,9 +177,10 @@ pub async fn item_details( templates.render_html("admin/items/item_detail.jinja", context) } -pub async fn create_item( +pub async fn new_item( templates: State, registry: State>, + headers: HeaderMap, Path((app_key, model_key)): Path<(String, String)>, ) -> impl IntoResponse { let context = if let Ok(repo) = registry.get_repository(&app_key, &model_key) { @@ -165,8 +188,8 @@ pub async fn create_item( .get_model(&app_key, &model_key) .expect("Admin Model not found?"); AdminContext { + base: base_template(&headers), 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), @@ -174,6 +197,42 @@ pub async fn create_item( } } else { AdminContext { + base: base_template(&headers), + available_apps: registry.get_apps(), + ..Default::default() + } + }; + templates.render_html("admin/items/item_create.jinja", context) +} + +pub async fn create_item( + templates: State, + registry: State>, + headers: HeaderMap, + Path((app_key, model_key)): Path<(String, String)>, + Form(form): Form, +) -> 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?"); + + // create our item. + let result = repo.create(&admin_model, form); + + // TODO: refactor run over these views, way too much repetition. + AdminContext { + base: base_template(&headers), + available_apps: registry.get_apps(), + item_info: Some(repo.info(&admin_model)), + item_list: repo.list(&admin_model), + item_model: Some(admin_model), + item: result, + ..Default::default() + } + } else { + AdminContext { + base: base_template(&headers), available_apps: registry.get_apps(), ..Default::default() } diff --git a/templates/admin/base.jinja b/templates/admin/base.jinja index b77d623..38dc478 100644 --- a/templates/admin/base.jinja +++ b/templates/admin/base.jinja @@ -107,13 +107,12 @@
{% if model.admin_url %} - {{ model.name }} + {{ model.name }} {% else %} {{ model.name }} {% endif %} - {% if model.add_url %} {{ translate( 'Add') }} {% endif %} @@ -130,7 +129,7 @@ {% endblock sidebar %}
-
+
{% block content %} {% endblock content %}
diff --git a/templates/admin/base_hx.jinja b/templates/admin/base_hx.jinja new file mode 100644 index 0000000..9337d59 --- /dev/null +++ b/templates/admin/base_hx.jinja @@ -0,0 +1,2 @@ +{% block content %} +{% endblock content %} \ No newline at end of file diff --git a/templates/admin/items/item_create.jinja b/templates/admin/items/item_create.jinja index 1d2c546..d0e5427 100644 --- a/templates/admin/items/item_create.jinja +++ b/templates/admin/items/item_create.jinja @@ -1,4 +1,4 @@ -{% extends "admin/base.jinja" %} +{% extends base|none("admin/base.jinja") %} {% macro input(name, value="", type="text") -%} @@ -7,7 +7,7 @@ {% block content %} Create {{item_model.name}} in {{item_info.name}} -
+ {% set fields = item_info.display_list %} {% for field in fields %}

{{ input(field) }}

diff --git a/templates/admin/items/item_detail.jinja b/templates/admin/items/item_detail.jinja index df50696..149af85 100644 --- a/templates/admin/items/item_detail.jinja +++ b/templates/admin/items/item_detail.jinja @@ -1,4 +1,4 @@ -{% extends "admin/base.jinja" %} +{% extends base|none("admin/base.jinja") %} {% block content %} diff --git a/templates/admin/items/item_list.jinja b/templates/admin/items/item_list.jinja index 2f5af61..c20983d 100644 --- a/templates/admin/items/item_list.jinja +++ b/templates/admin/items/item_list.jinja @@ -1,4 +1,4 @@ -{% extends "admin/base.jinja" %} +{% extends base|none("admin/base.jinja") %} {% block content %} @@ -39,7 +39,8 @@ {% for key in item_keys %} {% if key==primary_key %} - {% if item.detail_url %}{{ + {% if item.detail_url %}{{ item.fields[key] }}{% else %}{{item.fields[key] }}{% endif %} {% else %}