use super::AdminModel;
#[derive(Debug, Clone)]
pub struct Fieldset {
pub title: &'static str,
pub fields: &'static [&'static str],
}
#[derive(Debug, Clone)]
pub struct Inline {
pub target_model: &'static str,
pub fk_field: &'static str,
pub label: Option<&'static str>,
pub max_rows: usize,
pub display_field: Option<&'static str>,
}
#[derive(Debug, Clone)]
pub struct FieldValidationError {
pub field: Option<&'static str>,
pub message: String,
}
impl FieldValidationError {
pub fn field(field: &'static str, message: impl Into<String>) -> Self {
Self {
field: Some(field),
message: message.into(),
}
}
pub fn global(message: impl Into<String>) -> Self {
Self {
field: None,
message: message.into(),
}
}
}
pub trait ModelAdmin: AdminModel {
fn list_display() -> &'static [&'static str] {
&[]
}
fn list_filter() -> &'static [&'static str] {
&[]
}
fn search_fields() -> &'static [&'static str] {
&[]
}
fn search_index_column() -> Option<&'static str> {
None
}
fn ordering() -> &'static [&'static str] {
&["-id"]
}
fn list_per_page() -> usize {
50
}
fn readonly_fields() -> &'static [&'static str] {
&[]
}
fn inlines() -> &'static [Inline] {
&[]
}
fn fieldsets() -> &'static [Fieldset] {
&[]
}
fn validate(_model: &Self) -> std::result::Result<(), Vec<FieldValidationError>> {
Ok(())
}
fn bulk_actions() -> &'static [BulkAction] {
&[]
}
fn execute_bulk_action<'a>(
action: &'a str,
_ids: &'a [i64],
_db: &'a crate::orm::Db,
_ctx: &'a crate::admin::bulk::BulkActionContext<'a>,
) -> ::std::pin::Pin<
::std::boxed::Box<
dyn ::std::future::Future<
Output = crate::error::Result<crate::admin::bulk::BulkActionResult>,
> + ::std::marker::Send
+ 'a,
>,
> {
let owned = action.to_string();
Box::pin(async move {
Err(crate::error::Error::BadRequest(format!(
"bulk action `{owned}` has no project handler — \
override `ModelAdmin::execute_bulk_action` on this model"
)))
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct BulkAction {
pub name: &'static str,
pub label: &'static str,
pub destructive: bool,
pub confirm: bool,
pub permission: Option<&'static str>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SortDir {
Asc,
Desc,
}
impl SortDir {
pub fn sql(self) -> &'static str {
match self {
SortDir::Asc => "ASC",
SortDir::Desc => "DESC",
}
}
}
pub fn parse_order_spec(spec: &str) -> (String, SortDir) {
if let Some(rest) = spec.strip_prefix('-') {
(rest.to_string(), SortDir::Desc)
} else {
(spec.to_string(), SortDir::Asc)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_order_spec_handles_leading_minus() {
assert_eq!(parse_order_spec("-id"), ("id".to_string(), SortDir::Desc));
assert_eq!(parse_order_spec("name"), ("name".to_string(), SortDir::Asc));
}
#[test]
fn sort_dir_sql_is_stable() {
assert_eq!(SortDir::Asc.sql(), "ASC");
assert_eq!(SortDir::Desc.sql(), "DESC");
}
#[test]
fn search_index_column_default_is_none() {
struct Stub;
impl crate::admin::types::AdminModel for Stub {
const ADMIN_NAME: &'static str = "s";
const DISPLAY_NAME: &'static str = "S";
const SINGULAR_NAME: &'static str = "S";
const FIELDS: &'static [crate::admin::types::AdminField] = &[];
fn id(&self) -> i64 {
0
}
fn from_form(_: &crate::http::FormData) -> std::result::Result<Self, Vec<String>> {
Err(vec![])
}
fn display_values(&self) -> Vec<(String, String)> {
vec![]
}
fn object_label(&self) -> String {
String::new()
}
fn values_to_update(&self) -> Vec<(&'static str, crate::orm::Value)> {
vec![]
}
}
impl ModelAdmin for Stub {}
assert_eq!(<Stub as ModelAdmin>::search_index_column(), None);
}
}