#[cfg(client)]
pub use wasm_only::*;
#[cfg(client)]
mod wasm_only {
use std::collections::HashMap;
use crate::types::{
AdminAction, AdminActionOutcome, AdminError, AdminResult, Fieldset, FormFieldOverride,
InlineStyle, PrepopulatedField,
};
use reinhardt_core::model_form::ModelFormTableName;
use std::collections::HashMap;
use std::fmt::Debug;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdminFormMode {
Create,
Update,
}
pub type AdminFormData = HashMap<String, serde_json::Value>;
pub type AdminFormResult<T> = Result<T, AdminFormErrors>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AdminFormError {
field: Option<String>,
message: String,
}
impl AdminFormError {
pub fn field(&self) -> Option<&str> {
self.field.as_deref()
}
pub fn message(&self) -> &str {
&self.message
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AdminFormErrors {
errors: Vec<AdminFormError>,
}
impl AdminFormErrors {
pub fn field(field: impl Into<String>, message: impl Into<String>) -> Self {
let mut errors = Self::default();
errors.push_field(field, message);
errors
}
pub fn global(message: impl Into<String>) -> Self {
let mut errors = Self::default();
errors.push_global(message);
errors
}
pub fn push_field(&mut self, field: impl Into<String>, message: impl Into<String>) {
self.errors.push(AdminFormError {
field: Some(field.into()),
message: message.into(),
});
}
pub fn push_global(&mut self, message: impl Into<String>) {
self.errors.push(AdminFormError {
field: None,
message: message.into(),
});
}
pub fn iter(&self) -> impl Iterator<Item = &AdminFormError> {
self.errors.iter()
}
pub fn is_empty(&self) -> bool {
self.errors.is_empty()
}
}
pub trait AdminForm: Debug + Send + Sync {
fn schema(&self) -> Vec<FormFieldOverride> {
Vec::new()
}
fn normalize(
&self,
_mode: AdminFormMode,
data: AdminFormData,
) -> AdminFormResult<AdminFormData> {
Ok(data)
}
fn validate(&self, _mode: AdminFormMode, _data: &AdminFormData) -> AdminFormResult<()> {
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct InlineModelAdmin {
key: String,
child_model: String,
foreign_key: String,
fields: Vec<String>,
style: InlineStyle,
extra: usize,
can_delete: bool,
}
impl InlineModelAdmin {
pub fn new<P, C>(
child_model: impl Into<String>,
foreign_key: impl Into<String>,
fields: &[&str],
) -> AdminResult<Self>
where
C: ModelFormTableName,
{
let _ = std::marker::PhantomData::<(P, C)>;
let child_model = child_model.into();
let foreign_key = foreign_key.into();
Ok(Self {
key: format!(
"{}-{}",
identifier_part(<C as ModelFormTableName>::table_name()),
identifier_part(&foreign_key)
),
child_model,
foreign_key,
fields: fields.iter().map(|field| (*field).to_owned()).collect(),
style: InlineStyle::Tabular,
extra: 0,
can_delete: false,
})
}
pub fn style(mut self, style: InlineStyle) -> Self {
self.style = style;
self
}
pub fn extra(mut self, extra: usize) -> Self {
self.extra = extra.min(100);
self
}
pub fn can_delete(mut self, can_delete: bool) -> Self {
self.can_delete = can_delete;
self
}
pub fn key(&self) -> &str {
&self.key
}
pub fn child_model(&self) -> &str {
&self.child_model
}
pub fn foreign_key(&self) -> &str {
&self.foreign_key
}
pub fn fields(&self) -> &[String] {
&self.fields
}
pub fn style_value(&self) -> InlineStyle {
self.style
}
pub fn extra_rows(&self) -> usize {
self.extra
}
pub fn delete_enabled(&self) -> bool {
self.can_delete
}
}
fn identifier_part(value: &str) -> String {
value
.chars()
.map(|character| {
if character.is_ascii_alphanumeric() || matches!(character, '_' | '-') {
character.to_ascii_lowercase()
} else {
'_'
}
})
.collect::<String>()
.trim_matches('_')
.to_owned()
}
pub struct AdminSite;
pub struct AdminDatabase;
pub struct AdminActionTransaction;
pub struct AdminRecord;
pub struct AdminQuery;
pub struct AdminRequestContext;
pub trait AdminUser: Send + Sync {
fn is_active(&self) -> bool;
fn is_staff(&self) -> bool;
fn is_superuser(&self) -> bool;
fn get_username(&self) -> &str;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ListColumn {
Field {
field: String,
label: String,
},
Computed {
key: String,
label: String,
sort_field: Option<String>,
},
}
#[async_trait::async_trait]
pub trait ModelAdmin: Send + Sync {
fn model_name(&self) -> &str;
fn table_name(&self) -> &str {
""
}
fn pk_field(&self) -> &str {
"id"
}
fn list_display(&self) -> Vec<&str> {
vec!["id"]
}
fn list_columns(&self) -> Vec<ListColumn> {
self.list_display()
.into_iter()
.map(|field| ListColumn::Field {
field: field.to_string(),
label: field.to_string(),
})
.collect()
}
fn computed_list_value(
&self,
key: &str,
_row: &HashMap<String, serde_json::Value>,
) -> crate::types::AdminResult<serde_json::Value> {
Err(crate::types::AdminError::TemplateError(format!(
"No computed list column is configured for key '{key}'"
)))
}
fn date_hierarchy(&self) -> Option<&str> {
None
}
fn list_editable(&self) -> Vec<&str> {
vec![]
}
fn list_filter(&self) -> Vec<&str> {
vec![]
}
fn search_fields(&self) -> Vec<&str> {
vec![]
}
fn filter_horizontal(&self) -> Vec<&str> {
vec![]
}
fn filter_vertical(&self) -> Vec<&str> {
vec![]
}
fn fields(&self) -> Option<Vec<&str>> {
None
}
fn fieldsets(&self) -> Option<Vec<Fieldset>> {
None
}
fn inlines(&self) -> Vec<InlineModelAdmin> {
Vec::new()
}
fn readonly_fields(&self) -> Vec<&str> {
vec![]
}
fn autocomplete_fields(&self) -> Vec<&str> {
vec![]
}
fn raw_id_fields(&self) -> Vec<&str> {
vec![]
}
fn form(&self) -> Option<&dyn AdminForm> {
None
}
fn formfield_overrides(&self) -> Vec<FormFieldOverride> {
Vec::new()
}
fn prepopulated_fields(&self) -> Vec<PrepopulatedField> {
Vec::new()
}
fn object_label(&self, _values: &HashMap<String, serde_json::Value>) -> Option<String> {
None
}
fn ordering(&self) -> Vec<&str> {
vec!["-id"]
}
fn list_per_page(&self) -> Option<usize> {
None
}
fn list_select_related(&self) -> Vec<&str> {
vec![]
}
async fn get_queryset(
&self,
_user: &dyn AdminUser,
_request: &AdminRequestContext,
query: AdminQuery,
) -> crate::types::AdminResult<AdminQuery> {
Ok(query)
}
fn actions(&self) -> Vec<AdminAction> {
Vec::new()
}
async fn execute_action(
&self,
action: &str,
_ids: &[String],
_transaction: &mut AdminActionTransaction,
_user: &dyn AdminUser,
) -> AdminResult<AdminActionOutcome> {
Err(AdminError::ValidationError(format!(
"Invalid action: {action}"
)))
}
async fn has_view_permission(&self, _user: &dyn AdminUser) -> bool {
false
}
async fn has_add_permission(&self, _user: &dyn AdminUser) -> bool {
false
}
async fn has_change_permission(&self, _user: &dyn AdminUser) -> bool {
false
}
async fn has_delete_permission(&self, _user: &dyn AdminUser) -> bool {
false
}
}
pub struct ModelAdminConfig;
pub struct ModelAdminConfigBuilder;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct ExportFormat;
pub struct ImportBuilder;
pub struct ImportError;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct ImportFormat;
pub struct ImportResult;
#[allow(dead_code)]
fn assert_admin_trait_shapes(
admin: &dyn ModelAdmin,
_user: &dyn AdminUser,
_query: AdminQuery,
_request: &AdminRequestContext,
record: &std::collections::HashMap<String, serde_json::Value>,
) {
let _: Option<String> = admin.object_label(record);
}
}