Skip to main content

reinhardt_forms/fields/
model_choice_field.rs

1//! ModelChoiceField and ModelMultipleChoiceField for ORM integration
2
3use crate::Widget;
4use crate::field::{FieldError, FieldResult, FormField};
5use crate::model_form::FormModel;
6use serde_json::Value;
7use std::collections::HashMap;
8use std::marker::PhantomData;
9
10/// A field for selecting a single model instance from a queryset
11///
12/// This field displays model instances as choices in a select widget.
13pub struct ModelChoiceField<T: FormModel> {
14	/// The field name used as the form data key.
15	pub name: String,
16	/// Whether a selection is required.
17	pub required: bool,
18	/// Custom error messages keyed by error type.
19	pub error_messages: HashMap<String, String>,
20	/// The widget type used for rendering this field.
21	pub widget: Widget,
22	/// Help text displayed alongside the field.
23	pub help_text: String,
24	/// Optional initial (default) value for the field.
25	pub initial: Option<Value>,
26	/// The list of model instances to choose from.
27	pub queryset: Vec<T>,
28	/// Label for the empty/default option (e.g., "Select one...").
29	pub empty_label: Option<String>,
30	_phantom: PhantomData<T>,
31}
32
33impl<T: FormModel> ModelChoiceField<T> {
34	/// Create a new ModelChoiceField
35	///
36	/// # Examples
37	///
38	/// ```
39	/// use reinhardt_forms::fields::ModelChoiceField;
40	/// use reinhardt_forms::FormField;
41	/// use reinhardt_forms::FormModel;
42	/// use serde_json::{json, Value};
43	///
44	/// // Define a simple Category model
45	/// #[derive(Clone)]
46	/// struct Category {
47	///     id: i32,
48	///     name: String,
49	/// }
50	///
51	/// impl FormModel for Category {
52	///     fn field_names() -> Vec<String> {
53	///         vec!["id".to_string(), "name".to_string()]
54	///     }
55	///
56	///     fn get_field(&self, name: &str) -> Option<Value> {
57	///         match name {
58	///             "id" => Some(json!(self.id)),
59	///             "name" => Some(json!(self.name)),
60	///             _ => None,
61	///         }
62	///     }
63	///
64	///     fn set_field(&mut self, _name: &str, _value: Value) -> Result<(), String> {
65	///         Ok(())
66	///     }
67	///
68	///     fn save(&mut self) -> Result<(), String> {
69	///         Ok(())
70	///     }
71	/// }
72	///
73	/// // Create a queryset with sample categories
74	/// let categories = vec![
75	///     Category { id: 1, name: "Technology".to_string() },
76	///     Category { id: 2, name: "Science".to_string() },
77	/// ];
78	///
79	/// let field = ModelChoiceField::new("category", categories);
80	/// assert_eq!(field.name(), "category");
81	/// assert!(FormField::required(&field));
82	/// ```
83	pub fn new(name: impl Into<String>, queryset: Vec<T>) -> Self {
84		let mut error_messages = HashMap::new();
85		error_messages.insert(
86			"required".to_string(),
87			"This field is required.".to_string(),
88		);
89		error_messages.insert(
90			"invalid_choice".to_string(),
91			"Select a valid choice.".to_string(),
92		);
93
94		Self {
95			name: name.into(),
96			required: true,
97			error_messages,
98			widget: Widget::Select {
99				choices: Vec::new(),
100			},
101			help_text: String::new(),
102			initial: None,
103			queryset,
104			empty_label: Some("--------".to_string()),
105			_phantom: PhantomData,
106		}
107	}
108	/// Sets whether a selection is required.
109	pub fn required(mut self, required: bool) -> Self {
110		self.required = required;
111		self
112	}
113	/// Sets the help text displayed alongside the field.
114	pub fn help_text(mut self, text: impl Into<String>) -> Self {
115		self.help_text = text.into();
116		self
117	}
118	/// Sets the initial (default) value.
119	pub fn initial(mut self, value: Value) -> Self {
120		self.initial = Some(value);
121		self
122	}
123	/// Sets the label for the empty/default option.
124	pub fn empty_label(mut self, label: Option<String>) -> Self {
125		self.empty_label = label;
126		self
127	}
128	/// Overrides the error message for a specific error type.
129	pub fn error_message(
130		mut self,
131		error_type: impl Into<String>,
132		message: impl Into<String>,
133	) -> Self {
134		self.error_messages
135			.insert(error_type.into(), message.into());
136		self
137	}
138
139	/// Get choices from queryset
140	/// Converts model instances to (value, label) pairs for display in select widget
141	// Allow dead_code: API reserved for future widget rendering integration
142	#[allow(dead_code)]
143	fn get_choices(&self) -> Vec<(String, String)> {
144		let mut choices = Vec::new();
145
146		if !self.required && self.empty_label.is_some() {
147			choices.push(("".to_string(), self.empty_label.clone().unwrap()));
148		}
149
150		// Convert queryset items to choices
151		for instance in &self.queryset {
152			let value = instance.to_choice_value();
153			let label = instance.to_choice_label();
154			choices.push((value, label));
155		}
156
157		choices
158	}
159}
160
161impl<T: FormModel> FormField for ModelChoiceField<T> {
162	fn name(&self) -> &str {
163		&self.name
164	}
165
166	fn label(&self) -> Option<&str> {
167		None
168	}
169
170	fn widget(&self) -> &Widget {
171		&self.widget
172	}
173
174	fn required(&self) -> bool {
175		self.required
176	}
177
178	fn initial(&self) -> Option<&Value> {
179		self.initial.as_ref()
180	}
181
182	fn help_text(&self) -> Option<&str> {
183		if self.help_text.is_empty() {
184			None
185		} else {
186			Some(&self.help_text)
187		}
188	}
189
190	fn clean(&self, value: Option<&Value>) -> FieldResult<Value> {
191		if value.is_none() || value == Some(&Value::Null) {
192			if self.required {
193				let error_msg = self
194					.error_messages
195					.get("required")
196					.cloned()
197					.unwrap_or_else(|| "This field is required.".to_string());
198				return Err(FieldError::validation(None, &error_msg));
199			}
200			return Ok(Value::Null);
201		}
202
203		let s = match value.unwrap() {
204			Value::String(s) => s.as_str(),
205			Value::Number(n) => {
206				// Convert number to string for validation
207				&n.to_string()
208			}
209			_ => {
210				let error_msg = self
211					.error_messages
212					.get("invalid_choice")
213					.cloned()
214					.unwrap_or_else(|| "Select a valid choice.".to_string());
215				return Err(FieldError::validation(None, &error_msg));
216			}
217		};
218
219		if s.is_empty() {
220			if self.required {
221				let error_msg = self
222					.error_messages
223					.get("required")
224					.cloned()
225					.unwrap_or_else(|| "This field is required.".to_string());
226				return Err(FieldError::validation(None, &error_msg));
227			}
228			return Ok(Value::Null);
229		}
230
231		// Validate that the choice exists in queryset
232		let choice_exists = self
233			.queryset
234			.iter()
235			.any(|instance| instance.to_choice_value() == s);
236
237		if !choice_exists {
238			let error_msg = self
239				.error_messages
240				.get("invalid_choice")
241				.cloned()
242				.unwrap_or_else(|| "Select a valid choice.".to_string());
243			return Err(FieldError::validation(None, &error_msg));
244		}
245
246		Ok(Value::String(s.to_string()))
247	}
248
249	fn has_changed(&self, initial: Option<&Value>, data: Option<&Value>) -> bool {
250		match (initial, data) {
251			(None, None) => false,
252			(Some(_), None) | (None, Some(_)) => true,
253			(Some(a), Some(b)) => a != b,
254		}
255	}
256}
257
258/// A field for selecting multiple model instances from a queryset
259///
260/// This field displays model instances as choices in a multiple select widget.
261pub struct ModelMultipleChoiceField<T: FormModel> {
262	/// The field name used as the form data key.
263	pub name: String,
264	/// Whether at least one selection is required.
265	pub required: bool,
266	/// Custom error messages keyed by error type.
267	pub error_messages: HashMap<String, String>,
268	/// The widget type used for rendering this field.
269	pub widget: Widget,
270	/// Help text displayed alongside the field.
271	pub help_text: String,
272	/// Optional initial (default) value for the field.
273	pub initial: Option<Value>,
274	/// The list of model instances to choose from.
275	pub queryset: Vec<T>,
276	_phantom: PhantomData<T>,
277}
278
279impl<T: FormModel> ModelMultipleChoiceField<T> {
280	/// Create a new ModelMultipleChoiceField
281	///
282	/// # Examples
283	///
284	/// ```
285	/// use reinhardt_forms::fields::ModelMultipleChoiceField;
286	/// use reinhardt_forms::FormField;
287	/// use reinhardt_forms::FormModel;
288	/// use serde_json::{json, Value};
289	///
290	/// // Define a simple Tag model
291	/// #[derive(Clone)]
292	/// struct Tag {
293	///     id: i32,
294	///     name: String,
295	/// }
296	///
297	/// impl FormModel for Tag {
298	///     fn field_names() -> Vec<String> {
299	///         vec!["id".to_string(), "name".to_string()]
300	///     }
301	///
302	///     fn get_field(&self, name: &str) -> Option<Value> {
303	///         match name {
304	///             "id" => Some(json!(self.id)),
305	///             "name" => Some(json!(self.name)),
306	///             _ => None,
307	///         }
308	///     }
309	///
310	///     fn set_field(&mut self, _name: &str, _value: Value) -> Result<(), String> {
311	///         Ok(())
312	///     }
313	///
314	///     fn save(&mut self) -> Result<(), String> {
315	///         Ok(())
316	///     }
317	/// }
318	///
319	/// // Create a queryset with sample tags
320	/// let tags = vec![
321	///     Tag { id: 1, name: "rust".to_string() },
322	///     Tag { id: 2, name: "programming".to_string() },
323	///     Tag { id: 3, name: "web".to_string() },
324	/// ];
325	///
326	/// let field = ModelMultipleChoiceField::new("tags", tags);
327	/// assert_eq!(field.name(), "tags");
328	/// assert!(FormField::required(&field));
329	///
330	/// // Test with multiple selections
331	/// let result = field.clean(Some(&json!(["1", "2"])));
332	/// assert!(result.is_ok());
333	/// ```
334	pub fn new(name: impl Into<String>, queryset: Vec<T>) -> Self {
335		let mut error_messages = HashMap::new();
336		error_messages.insert(
337			"required".to_string(),
338			"This field is required.".to_string(),
339		);
340		error_messages.insert(
341			"invalid_choice".to_string(),
342			"Select a valid choice.".to_string(),
343		);
344		error_messages.insert(
345			"invalid_list".to_string(),
346			"Enter a list of values.".to_string(),
347		);
348
349		Self {
350			name: name.into(),
351			required: true,
352			error_messages,
353			widget: Widget::Select {
354				choices: Vec::new(),
355			},
356			help_text: String::new(),
357			initial: None,
358			queryset,
359			_phantom: PhantomData,
360		}
361	}
362	/// Sets whether at least one selection is required.
363	pub fn required(mut self, required: bool) -> Self {
364		self.required = required;
365		self
366	}
367	/// Sets the help text displayed alongside the field.
368	pub fn help_text(mut self, text: impl Into<String>) -> Self {
369		self.help_text = text.into();
370		self
371	}
372	/// Sets the initial (default) value.
373	pub fn initial(mut self, value: Value) -> Self {
374		self.initial = Some(value);
375		self
376	}
377	/// Overrides the error message for a specific error type.
378	pub fn error_message(
379		mut self,
380		error_type: impl Into<String>,
381		message: impl Into<String>,
382	) -> Self {
383		self.error_messages
384			.insert(error_type.into(), message.into());
385		self
386	}
387
388	/// Get choices from queryset
389	// Allow dead_code: API reserved for future widget rendering integration
390	#[allow(dead_code)]
391	fn get_choices(&self) -> Vec<(String, String)> {
392		let mut choices = Vec::new();
393
394		// Convert queryset items to choices
395		for instance in &self.queryset {
396			let value = instance.to_choice_value();
397			let label = instance.to_choice_label();
398			choices.push((value, label));
399		}
400
401		choices
402	}
403}
404
405impl<T: FormModel> FormField for ModelMultipleChoiceField<T> {
406	fn name(&self) -> &str {
407		&self.name
408	}
409
410	fn label(&self) -> Option<&str> {
411		None
412	}
413
414	fn widget(&self) -> &Widget {
415		&self.widget
416	}
417
418	fn required(&self) -> bool {
419		self.required
420	}
421
422	fn initial(&self) -> Option<&Value> {
423		self.initial.as_ref()
424	}
425
426	fn help_text(&self) -> Option<&str> {
427		if self.help_text.is_empty() {
428			None
429		} else {
430			Some(&self.help_text)
431		}
432	}
433
434	fn clean(&self, value: Option<&Value>) -> FieldResult<Value> {
435		if value.is_none() || value == Some(&Value::Null) {
436			if self.required {
437				let error_msg = self
438					.error_messages
439					.get("required")
440					.cloned()
441					.unwrap_or_else(|| "This field is required.".to_string());
442				return Err(FieldError::validation(None, &error_msg));
443			}
444			return Ok(Value::Array(Vec::new()));
445		}
446
447		let values = match value.unwrap() {
448			Value::Array(arr) => arr.clone(),
449			Value::String(s) if s.is_empty() => {
450				if self.required {
451					let error_msg = self
452						.error_messages
453						.get("required")
454						.cloned()
455						.unwrap_or_else(|| "This field is required.".to_string());
456					return Err(FieldError::validation(None, &error_msg));
457				}
458				return Ok(Value::Array(Vec::new()));
459			}
460			Value::String(s) => {
461				// Split comma-separated values
462				s.split(',')
463					.map(|v| Value::String(v.trim().to_string()))
464					.collect()
465			}
466			_ => {
467				let error_msg = self
468					.error_messages
469					.get("invalid_list")
470					.cloned()
471					.unwrap_or_else(|| "Enter a list of values.".to_string());
472				return Err(FieldError::validation(None, &error_msg));
473			}
474		};
475
476		if values.is_empty() && self.required {
477			let error_msg = self
478				.error_messages
479				.get("required")
480				.cloned()
481				.unwrap_or_else(|| "This field is required.".to_string());
482			return Err(FieldError::validation(None, &error_msg));
483		}
484
485		// Validate that all choices exist in queryset
486		for value in &values {
487			if let Some(value_str) = value.as_str() {
488				let choice_exists = self
489					.queryset
490					.iter()
491					.any(|instance| instance.to_choice_value() == value_str);
492
493				if !choice_exists {
494					let error_msg = self
495						.error_messages
496						.get("invalid_choice")
497						.cloned()
498						.unwrap_or_else(|| format!("'{}' is not a valid choice.", value_str));
499					return Err(FieldError::validation(None, &error_msg));
500				}
501			}
502		}
503
504		Ok(Value::Array(values))
505	}
506
507	fn has_changed(&self, initial: Option<&Value>, data: Option<&Value>) -> bool {
508		match (initial, data) {
509			(None, None) => false,
510			(Some(_), None) | (None, Some(_)) => true,
511			(Some(Value::Array(a)), Some(Value::Array(b))) => {
512				if a.len() != b.len() {
513					return true;
514				}
515				a.iter().zip(b.iter()).any(|(x, y)| x != y)
516			}
517			(Some(a), Some(b)) => a != b,
518		}
519	}
520}
521
522#[cfg(test)]
523mod tests {
524	use super::*;
525	use crate::FormField;
526	use serde_json::json;
527
528	// Mock model for testing
529	struct TestModel {
530		id: i32,
531		name: String,
532	}
533
534	impl FormModel for TestModel {
535		fn field_names() -> Vec<String> {
536			vec!["id".to_string(), "name".to_string()]
537		}
538
539		fn get_field(&self, name: &str) -> Option<Value> {
540			match name {
541				"id" => Some(Value::Number(self.id.into())),
542				"name" => Some(Value::String(self.name.clone())),
543				_ => None,
544			}
545		}
546
547		fn set_field(&mut self, _name: &str, _value: Value) -> Result<(), String> {
548			Ok(())
549		}
550
551		fn save(&mut self) -> Result<(), String> {
552			Ok(())
553		}
554	}
555
556	#[test]
557	fn test_model_choice_field_basic() {
558		let queryset = vec![
559			TestModel {
560				id: 1,
561				name: "Option 1".to_string(),
562			},
563			TestModel {
564				id: 2,
565				name: "Option 2".to_string(),
566			},
567		];
568
569		let field = ModelChoiceField::new("choice", queryset);
570
571		assert_eq!(field.name(), "choice");
572		assert!(FormField::required(&field));
573	}
574
575	#[test]
576	fn test_model_choice_field_required() {
577		let field = ModelChoiceField::new("choice", Vec::<TestModel>::new());
578
579		let result = field.clean(None);
580		assert!(result.is_err());
581	}
582
583	#[test]
584	fn test_model_choice_field_not_required() {
585		let field = ModelChoiceField::new("choice", Vec::<TestModel>::new()).required(false);
586
587		let result = field.clean(None);
588		assert!(result.is_ok());
589		assert_eq!(result.unwrap(), Value::Null);
590	}
591
592	#[test]
593	fn test_model_multiple_choice_field_basic() {
594		let queryset = vec![
595			TestModel {
596				id: 1,
597				name: "Option 1".to_string(),
598			},
599			TestModel {
600				id: 2,
601				name: "Option 2".to_string(),
602			},
603		];
604
605		let field = ModelMultipleChoiceField::new("choices", queryset);
606
607		assert_eq!(field.name(), "choices");
608		assert!(FormField::required(&field));
609	}
610
611	#[test]
612	fn test_model_multiple_choice_field_array() {
613		let queryset = vec![
614			TestModel {
615				id: 1,
616				name: "Option 1".to_string(),
617			},
618			TestModel {
619				id: 2,
620				name: "Option 2".to_string(),
621			},
622			TestModel {
623				id: 3,
624				name: "Option 3".to_string(),
625			},
626		];
627
628		let field = ModelMultipleChoiceField::new("choices", queryset).required(false);
629
630		let result = field.clean(Some(&json!(["1", "2"])));
631		assert!(result.is_ok());
632
633		if let Value::Array(arr) = result.unwrap() {
634			assert_eq!(arr.len(), 2);
635		} else {
636			panic!("Expected array");
637		}
638	}
639
640	#[test]
641	fn test_model_multiple_choice_field_comma_separated() {
642		let queryset = vec![
643			TestModel {
644				id: 1,
645				name: "Option 1".to_string(),
646			},
647			TestModel {
648				id: 2,
649				name: "Option 2".to_string(),
650			},
651			TestModel {
652				id: 3,
653				name: "Option 3".to_string(),
654			},
655		];
656
657		let field = ModelMultipleChoiceField::new("choices", queryset).required(false);
658
659		let result = field.clean(Some(&json!("1,2,3")));
660		assert!(result.is_ok());
661
662		if let Value::Array(arr) = result.unwrap() {
663			assert_eq!(arr.len(), 3);
664		} else {
665			panic!("Expected array");
666		}
667	}
668}