Skip to main content

reinhardt_forms/
model_formset.rs

1//! ModelFormSet implementation for managing multiple model forms
2//!
3//! ModelFormSets allow editing multiple model instances at once, handling
4//! creation, updates, and deletion in a single form submission.
5
6use crate::FormError;
7use crate::formset::FormSet;
8use crate::model_form::{FormModel, ModelForm, ModelFormConfig};
9use std::collections::HashMap;
10use std::marker::PhantomData;
11
12/// Configuration for ModelFormSet
13#[derive(Debug, Clone)]
14pub struct ModelFormSetConfig {
15	/// Configuration for individual model forms
16	pub form_config: ModelFormConfig,
17	/// Allow deletion of instances
18	pub can_delete: bool,
19	/// Allow ordering of instances
20	pub can_order: bool,
21	/// Number of extra forms to display
22	pub extra: usize,
23	/// Maximum number of forms
24	pub max_num: Option<usize>,
25	/// Minimum number of forms
26	pub min_num: usize,
27}
28
29impl Default for ModelFormSetConfig {
30	fn default() -> Self {
31		Self {
32			form_config: ModelFormConfig::default(),
33			can_delete: false,
34			can_order: false,
35			extra: 1,
36			max_num: Some(1000),
37			min_num: 0,
38		}
39	}
40}
41
42impl ModelFormSetConfig {
43	/// Create a new ModelFormSetConfig
44	///
45	/// # Examples
46	///
47	/// ```
48	/// use reinhardt_forms::ModelFormSetConfig;
49	///
50	/// let config = ModelFormSetConfig::new();
51	/// assert_eq!(config.extra, 1);
52	/// assert!(!config.can_delete);
53	/// ```
54	pub fn new() -> Self {
55		Self::default()
56	}
57	/// Set the number of extra forms
58	///
59	/// # Examples
60	///
61	/// ```
62	/// use reinhardt_forms::ModelFormSetConfig;
63	///
64	/// let config = ModelFormSetConfig::new().with_extra(3);
65	/// assert_eq!(config.extra, 3);
66	/// ```
67	pub fn with_extra(mut self, extra: usize) -> Self {
68		self.extra = extra;
69		self
70	}
71	/// Enable or disable deletion
72	///
73	/// # Examples
74	///
75	/// ```
76	/// use reinhardt_forms::ModelFormSetConfig;
77	///
78	/// let config = ModelFormSetConfig::new().with_can_delete(true);
79	/// assert!(config.can_delete);
80	/// ```
81	pub fn with_can_delete(mut self, can_delete: bool) -> Self {
82		self.can_delete = can_delete;
83		self
84	}
85	/// Enable or disable ordering
86	///
87	/// # Examples
88	///
89	/// ```
90	/// use reinhardt_forms::ModelFormSetConfig;
91	///
92	/// let config = ModelFormSetConfig::new().with_can_order(true);
93	/// assert!(config.can_order);
94	/// ```
95	pub fn with_can_order(mut self, can_order: bool) -> Self {
96		self.can_order = can_order;
97		self
98	}
99	/// Set maximum number of forms
100	///
101	/// # Examples
102	///
103	/// ```
104	/// use reinhardt_forms::ModelFormSetConfig;
105	///
106	/// let config = ModelFormSetConfig::new().with_max_num(Some(10));
107	/// assert_eq!(config.max_num, Some(10));
108	/// ```
109	pub fn with_max_num(mut self, max_num: Option<usize>) -> Self {
110		self.max_num = max_num;
111		self
112	}
113	/// Set minimum number of forms
114	///
115	/// # Examples
116	///
117	/// ```
118	/// use reinhardt_forms::ModelFormSetConfig;
119	///
120	/// let config = ModelFormSetConfig::new().with_min_num(2);
121	/// assert_eq!(config.min_num, 2);
122	/// ```
123	pub fn with_min_num(mut self, min_num: usize) -> Self {
124		self.min_num = min_num;
125		self
126	}
127	/// Set the form configuration
128	///
129	/// # Examples
130	///
131	/// ```
132	/// use reinhardt_forms::{ModelFormSetConfig, ModelFormConfig};
133	///
134	/// let form_config = ModelFormConfig::new()
135	///     .fields(vec!["name".to_string(), "email".to_string()]);
136	/// let config = ModelFormSetConfig::new().with_form_config(form_config);
137	/// assert!(config.form_config.fields.is_some());
138	/// ```
139	pub fn with_form_config(mut self, form_config: ModelFormConfig) -> Self {
140		self.form_config = form_config;
141		self
142	}
143}
144
145/// A formset for managing multiple model instances
146pub struct ModelFormSet<T: FormModel> {
147	model_forms: Vec<ModelForm<T>>,
148	formset: FormSet,
149	_phantom: PhantomData<T>,
150}
151
152impl<T: FormModel> ModelFormSet<T> {
153	/// Create a new ModelFormSet with instances
154	///
155	/// # Examples
156	///
157	/// ```ignore
158	/// use reinhardt_forms::{ModelFormSet, ModelFormSetConfig};
159	///
160	/// let config = ModelFormSetConfig::new();
161	/// let instances = vec![]; // Empty list of model instances
162	/// let formset = ModelFormSet::<MyModel>::new("formset".to_string(), instances, config);
163	/// assert_eq!(formset.prefix(), "formset");
164	/// ```
165	pub fn new(prefix: String, instances: Vec<T>, config: ModelFormSetConfig) -> Self {
166		let mut model_forms = Vec::new();
167
168		// Create ModelForm for each instance
169		for instance in instances {
170			let model_form = ModelForm::new(Some(instance), config.form_config.clone());
171			model_forms.push(model_form);
172		}
173
174		// Add extra empty forms
175		for _ in 0..config.extra {
176			let model_form = ModelForm::empty(config.form_config.clone());
177			model_forms.push(model_form);
178		}
179
180		// Create FormSet for management data
181		let formset = FormSet::new(prefix)
182			.with_extra(config.extra)
183			.with_can_delete(config.can_delete)
184			.with_can_order(config.can_order)
185			.with_max_num(config.max_num)
186			.with_min_num(config.min_num);
187
188		Self {
189			model_forms,
190			formset,
191			_phantom: PhantomData,
192		}
193	}
194	/// Create an empty ModelFormSet (for creating new instances)
195	///
196	/// # Examples
197	///
198	/// ```ignore
199	/// use reinhardt_forms::{ModelFormSet, ModelFormSetConfig};
200	///
201	/// let config = ModelFormSetConfig::new().with_extra(3);
202	/// let formset = ModelFormSet::<MyModel>::empty("formset".to_string(), config);
203	/// assert_eq!(formset.total_form_count(), 3);
204	/// ```
205	pub fn empty(prefix: String, config: ModelFormSetConfig) -> Self {
206		Self::new(prefix, Vec::new(), config)
207	}
208	/// Returns the prefix used for form field naming.
209	pub fn prefix(&self) -> &str {
210		self.formset.prefix()
211	}
212	/// Returns references to all model instances that have been loaded.
213	pub fn instances(&self) -> Vec<&T> {
214		self.model_forms
215			.iter()
216			.filter_map(|form| form.instance())
217			.collect()
218	}
219	/// Returns the number of forms backed by existing model instances.
220	pub fn form_count(&self) -> usize {
221		// Return number of forms with instances (not including extra empty forms)
222		self.model_forms
223			.iter()
224			.filter(|form| form.instance().is_some())
225			.count()
226	}
227	/// Returns the total number of forms, including extra empty forms.
228	pub fn total_form_count(&self) -> usize {
229		// Return total number of forms including extras
230		self.model_forms.len()
231	}
232	/// Validate all forms in the formset
233	///
234	/// # Examples
235	///
236	/// ```ignore
237	/// use reinhardt_forms::{ModelFormSet, ModelFormSetConfig};
238	///
239	/// let config = ModelFormSetConfig::new();
240	/// let mut formset = ModelFormSet::<MyModel>::empty("formset".to_string(), config);
241	/// let is_valid = formset.is_valid();
242	/// ```
243	pub fn is_valid(&mut self) -> bool {
244		// Validate all model forms
245		self.model_forms.iter_mut().all(|form| form.is_valid())
246	}
247	/// Collects and returns all validation errors from every form in the set.
248	pub fn errors(&self) -> Vec<String> {
249		// Collect errors from all model forms
250		self.model_forms
251			.iter()
252			.flat_map(|model_form| {
253				model_form
254					.form()
255					.errors()
256					.values()
257					.flat_map(|errors| errors.iter().cloned())
258			})
259			.collect()
260	}
261	/// Save all valid forms to the database
262	///
263	/// # Examples
264	///
265	/// ```ignore
266	/// use reinhardt_forms::{ModelFormSet, ModelFormSetConfig};
267	///
268	/// let config = ModelFormSetConfig::new();
269	/// let mut formset = ModelFormSet::<MyModel>::empty("formset".to_string(), config);
270	/// let result = formset.save();
271	/// ```
272	pub fn save(&mut self) -> Result<Vec<T>, FormError> {
273		if !self.is_valid() {
274			return Err(FormError::Validation("Formset is not valid".to_string()));
275		}
276
277		let mut saved_instances = Vec::new();
278
279		// Iterate through each ModelForm and save if it has changes
280		for model_form in &mut self.model_forms {
281			// Check if form has an instance (skip empty forms)
282			if model_form.instance().is_some() {
283				// Save the instance
284				let instance = model_form.save()?;
285				saved_instances.push(instance);
286			}
287		}
288
289		Ok(saved_instances)
290	}
291	/// Get management form data for HTML rendering
292	///
293	/// # Examples
294	///
295	/// ```ignore
296	/// use reinhardt_forms::{ModelFormSet, ModelFormSetConfig};
297	///
298	/// let config = ModelFormSetConfig::new().with_extra(2);
299	/// let formset = ModelFormSet::<MyModel>::empty("article".to_string(), config);
300	/// let mgmt_data = formset.management_form_data();
301	///
302	/// assert!(mgmt_data.contains_key("article-TOTAL_FORMS"));
303	/// assert_eq!(mgmt_data.get("article-TOTAL_FORMS"), Some(&"2".to_string()));
304	/// ```
305	pub fn management_form_data(&self) -> HashMap<String, String> {
306		self.formset.management_form_data()
307	}
308}
309
310/// Builder for creating ModelFormSet instances
311pub struct ModelFormSetBuilder<T: FormModel> {
312	config: ModelFormSetConfig,
313	_phantom: PhantomData<T>,
314}
315
316impl<T: FormModel> ModelFormSetBuilder<T> {
317	/// Create a new builder
318	///
319	/// # Examples
320	///
321	/// ```ignore
322	/// use reinhardt_forms::ModelFormSetBuilder;
323	///
324	/// let builder = ModelFormSetBuilder::<MyModel>::new();
325	/// ```
326	pub fn new() -> Self {
327		Self {
328			config: ModelFormSetConfig::default(),
329			_phantom: PhantomData,
330		}
331	}
332	/// Set the number of extra forms
333	///
334	/// # Examples
335	///
336	/// ```ignore
337	/// use reinhardt_forms::ModelFormSetBuilder;
338	///
339	/// let builder = ModelFormSetBuilder::<MyModel>::new().extra(5);
340	/// ```
341	pub fn extra(mut self, extra: usize) -> Self {
342		self.config.extra = extra;
343		self
344	}
345	/// Enable deletion
346	///
347	/// # Examples
348	///
349	/// ```ignore
350	/// use reinhardt_forms::ModelFormSetBuilder;
351	///
352	/// let builder = ModelFormSetBuilder::<MyModel>::new().can_delete(true);
353	/// ```
354	pub fn can_delete(mut self, can_delete: bool) -> Self {
355		self.config.can_delete = can_delete;
356		self
357	}
358	/// Enable ordering
359	///
360	/// # Examples
361	///
362	/// ```ignore
363	/// use reinhardt_forms::ModelFormSetBuilder;
364	///
365	/// let builder = ModelFormSetBuilder::<MyModel>::new().can_order(true);
366	/// ```
367	pub fn can_order(mut self, can_order: bool) -> Self {
368		self.config.can_order = can_order;
369		self
370	}
371	/// Set maximum number of forms
372	///
373	/// # Examples
374	///
375	/// ```ignore
376	/// use reinhardt_forms::ModelFormSetBuilder;
377	///
378	/// let builder = ModelFormSetBuilder::<MyModel>::new().max_num(10);
379	/// ```
380	pub fn max_num(mut self, max_num: usize) -> Self {
381		self.config.max_num = Some(max_num);
382		self
383	}
384	/// Set minimum number of forms
385	///
386	/// # Examples
387	///
388	/// ```ignore
389	/// use reinhardt_forms::ModelFormSetBuilder;
390	///
391	/// let builder = ModelFormSetBuilder::<MyModel>::new().min_num(1);
392	/// ```
393	pub fn min_num(mut self, min_num: usize) -> Self {
394		self.config.min_num = min_num;
395		self
396	}
397	/// Build the formset with instances
398	///
399	/// # Examples
400	///
401	/// ```ignore
402	/// use reinhardt_forms::ModelFormSetBuilder;
403	///
404	/// let instances = vec![]; // Empty list of model instances
405	/// let builder = ModelFormSetBuilder::<MyModel>::new();
406	/// let formset = builder.build("formset".to_string(), instances);
407	/// ```
408	pub fn build(self, prefix: String, instances: Vec<T>) -> ModelFormSet<T> {
409		ModelFormSet::new(prefix, instances, self.config)
410	}
411	/// Build an empty formset
412	///
413	/// # Examples
414	///
415	/// ```ignore
416	/// use reinhardt_forms::ModelFormSetBuilder;
417	///
418	/// let builder = ModelFormSetBuilder::<MyModel>::new().extra(3);
419	/// let formset = builder.build_empty("formset".to_string());
420	/// ```
421	pub fn build_empty(self, prefix: String) -> ModelFormSet<T> {
422		ModelFormSet::empty(prefix, self.config)
423	}
424}
425
426impl<T: FormModel> Default for ModelFormSetBuilder<T> {
427	fn default() -> Self {
428		Self::new()
429	}
430}
431
432#[cfg(test)]
433mod tests {
434	use super::*;
435	use serde_json::Value;
436
437	// Mock model for testing
438	struct Article {
439		id: i32,
440		title: String,
441		content: String,
442	}
443
444	impl FormModel for Article {
445		fn field_names() -> Vec<String> {
446			vec!["id".to_string(), "title".to_string(), "content".to_string()]
447		}
448
449		fn get_field(&self, name: &str) -> Option<Value> {
450			match name {
451				"id" => Some(Value::Number(self.id.into())),
452				"title" => Some(Value::String(self.title.clone())),
453				"content" => Some(Value::String(self.content.clone())),
454				_ => None,
455			}
456		}
457
458		fn set_field(&mut self, name: &str, value: Value) -> Result<(), String> {
459			match name {
460				"id" => {
461					if let Value::Number(n) = value {
462						self.id = n.as_i64().unwrap() as i32;
463						Ok(())
464					} else {
465						Err("Invalid type for id".to_string())
466					}
467				}
468				"title" => {
469					if let Value::String(s) = value {
470						self.title = s;
471						Ok(())
472					} else {
473						Err("Invalid type for title".to_string())
474					}
475				}
476				"content" => {
477					if let Value::String(s) = value {
478						self.content = s;
479						Ok(())
480					} else {
481						Err("Invalid type for content".to_string())
482					}
483				}
484				_ => Err(format!("Unknown field: {}", name)),
485			}
486		}
487
488		fn save(&mut self) -> Result<(), String> {
489			// Mock save
490			Ok(())
491		}
492	}
493
494	#[test]
495	fn test_model_formset_config() {
496		let config = ModelFormSetConfig::new()
497			.with_extra(3)
498			.with_can_delete(true)
499			.with_max_num(Some(10))
500			.with_min_num(1);
501
502		assert_eq!(config.extra, 3);
503		assert!(config.can_delete);
504		assert_eq!(config.max_num, Some(10));
505		assert_eq!(config.min_num, 1);
506	}
507
508	#[test]
509	fn test_model_formset_empty() {
510		let config = ModelFormSetConfig::new().with_extra(2);
511		let formset = ModelFormSet::<Article>::empty("article".to_string(), config);
512
513		assert_eq!(formset.prefix(), "article");
514		assert_eq!(formset.instances().len(), 0);
515		assert_eq!(formset.total_form_count(), 2);
516	}
517
518	#[test]
519	fn test_model_formset_with_instances() {
520		let instances = vec![
521			Article {
522				id: 1,
523				title: "First Article".to_string(),
524				content: "Content 1".to_string(),
525			},
526			Article {
527				id: 2,
528				title: "Second Article".to_string(),
529				content: "Content 2".to_string(),
530			},
531		];
532
533		let config = ModelFormSetConfig::new();
534		let formset = ModelFormSet::new("article".to_string(), instances, config);
535
536		assert_eq!(formset.instances().len(), 2);
537		assert_eq!(formset.form_count(), 2);
538	}
539
540	#[test]
541	fn test_model_formset_builder() {
542		let formset = ModelFormSetBuilder::<Article>::new()
543			.extra(3)
544			.can_delete(true)
545			.max_num(5)
546			.build_empty("article".to_string());
547
548		assert_eq!(formset.total_form_count(), 3);
549	}
550
551	#[test]
552	fn test_model_formset_management_data() {
553		let config = ModelFormSetConfig::new().with_extra(2).with_min_num(1);
554		let formset = ModelFormSet::<Article>::empty("article".to_string(), config);
555
556		let mgmt_data = formset.management_form_data();
557
558		assert_eq!(mgmt_data.get("article-TOTAL_FORMS"), Some(&"2".to_string()));
559		assert_eq!(
560			mgmt_data.get("article-INITIAL_FORMS"),
561			Some(&"0".to_string())
562		);
563		assert_eq!(
564			mgmt_data.get("article-MIN_NUM_FORMS"),
565			Some(&"1".to_string())
566		);
567	}
568}