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