use super::FieldType;
#[derive(Debug, Clone, Copy)]
pub struct FieldSchema {
pub name: &'static str,
pub column: &'static str,
pub ty: FieldType,
pub nullable: bool,
pub primary_key: bool,
pub relation: Option<Relation>,
pub max_length: Option<u32>,
pub min: Option<i64>,
pub max: Option<i64>,
pub default: Option<&'static str>,
pub auto: bool,
pub unique: bool,
}
#[derive(Debug, Clone, Copy)]
pub enum Relation {
Fk { to: &'static str, on: &'static str },
O2O { to: &'static str, on: &'static str },
}
#[derive(Debug, Clone, Copy)]
pub struct GenericRelation {
pub name: &'static str,
pub ct_column: &'static str,
pub pk_column: &'static str,
}
#[derive(Debug, Clone, Copy)]
pub struct CompositeFkRelation {
pub name: &'static str,
pub to: &'static str,
pub from: &'static [&'static str],
pub on: &'static [&'static str],
}
#[derive(Debug, Clone, Copy)]
pub struct M2MRelation {
pub name: &'static str,
pub to: &'static str,
pub through: &'static str,
pub src_col: &'static str,
pub dst_col: &'static str,
}
#[derive(Debug, Clone, Copy)]
pub struct ModelSchema {
pub name: &'static str,
pub table: &'static str,
pub fields: &'static [FieldSchema],
pub display: Option<&'static str>,
pub app_label: Option<&'static str>,
pub admin: Option<&'static AdminConfig>,
pub soft_delete_column: Option<&'static str>,
pub permissions: bool,
pub audit_track: Option<&'static [&'static str]>,
pub m2m: &'static [M2MRelation],
pub indexes: &'static [IndexSchema],
pub check_constraints: &'static [CheckConstraint],
pub composite_relations: &'static [CompositeFkRelation],
pub generic_relations: &'static [GenericRelation],
}
#[derive(Debug, Clone, Copy)]
pub struct CheckConstraint {
pub name: &'static str,
pub expr: &'static str,
}
#[derive(Debug, Clone, Copy)]
pub struct IndexSchema {
pub name: &'static str,
pub columns: &'static [&'static str],
pub unique: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct AdminConfig {
pub list_display: &'static [&'static str],
pub search_fields: &'static [&'static str],
pub list_per_page: usize,
pub ordering: &'static [(&'static str, bool)],
pub readonly_fields: &'static [&'static str],
pub list_filter: &'static [&'static str],
pub actions: &'static [&'static str],
pub fieldsets: &'static [Fieldset],
}
#[derive(Debug, Clone, Copy)]
pub struct Fieldset {
pub title: &'static str,
pub fields: &'static [&'static str],
}
impl AdminConfig {
pub const DEFAULT: AdminConfig = AdminConfig {
list_display: &[],
search_fields: &[],
list_per_page: 0,
ordering: &[],
readonly_fields: &[],
list_filter: &[],
actions: &[],
fieldsets: &[],
};
}
impl ModelSchema {
#[must_use]
pub fn field(&self, name: &str) -> Option<&'static FieldSchema> {
self.fields.iter().find(|f| f.name == name)
}
#[must_use]
pub fn field_by_column(&self, column: &str) -> Option<&'static FieldSchema> {
self.fields.iter().find(|f| f.column == column)
}
#[must_use]
pub fn primary_key(&self) -> Option<&'static FieldSchema> {
self.fields.iter().find(|f| f.primary_key)
}
pub fn scalar_fields(&self) -> impl Iterator<Item = &'static FieldSchema> {
self.fields.iter()
}
#[must_use]
pub fn display_field(&self) -> Option<&'static FieldSchema> {
if let Some(name) = self.display {
return self.field(name);
}
self.primary_key()
}
pub fn searchable_fields(&self) -> impl Iterator<Item = &'static FieldSchema> {
self.fields.iter().filter(|f| {
matches!(f.ty, FieldType::String) && f.max_length.is_some() && f.relation.is_none()
})
}
}
pub trait Model: Sized + Send + Sync + 'static {
const SCHEMA: &'static ModelSchema;
}
#[doc(hidden)]
pub struct ModelEntry {
pub schema: &'static ModelSchema,
pub module_path: &'static str,
}
impl ModelEntry {
#[must_use]
pub fn resolved_app_label(&self) -> Option<&'static str> {
if let Some(label) = self.schema.app_label {
return Some(label);
}
infer_app_label_from_module_path(self.module_path)
}
}
#[must_use]
pub fn infer_app_label_from_module_path(path: &'static str) -> Option<&'static str> {
let mut parts = path.split("::");
let _crate_name = parts.next()?;
let candidate = parts.next()?;
if matches!(candidate, "models" | "views" | "urls" | "main") {
return None;
}
Some(candidate)
}
inventory::collect!(ModelEntry);
#[cfg(test)]
mod tests {
use super::infer_app_label_from_module_path as infer;
#[test]
fn infers_app_from_submodule() {
assert_eq!(infer("my_app::blog::models"), Some("blog"));
assert_eq!(infer("my_app::shop::models"), Some("shop"));
assert_eq!(infer("my_app::auth"), Some("auth"));
}
#[test]
fn returns_none_for_project_root_models() {
assert_eq!(infer("my_app"), None);
assert_eq!(infer("my_app::models"), None);
assert_eq!(infer("my_app::views"), None);
}
}