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	pub fn prefix(&self) -> &str {
209		self.formset.prefix()
210	}
211	pub fn instances(&self) -> Vec<&T> {
212		self.model_forms
213			.iter()
214			.filter_map(|form| form.instance())
215			.collect()
216	}
217	pub fn form_count(&self) -> usize {
218		// Return number of forms with instances (not including extra empty forms)
219		self.model_forms
220			.iter()
221			.filter(|form| form.instance().is_some())
222			.count()
223	}
224	pub fn total_form_count(&self) -> usize {
225		// Return total number of forms including extras
226		self.model_forms.len()
227	}
228	/// Validate all forms in the formset
229	///
230	/// # Examples
231	///
232	/// ```ignore
233	/// use reinhardt_forms::{ModelFormSet, ModelFormSetConfig};
234	///
235	/// let config = ModelFormSetConfig::new();
236	/// let mut formset = ModelFormSet::<MyModel>::empty("formset".to_string(), config);
237	/// let is_valid = formset.is_valid();
238	/// ```
239	pub fn is_valid(&mut self) -> bool {
240		// Validate all model forms
241		self.model_forms.iter_mut().all(|form| form.is_valid())
242	}
243	pub fn errors(&self) -> Vec<String> {
244		// Collect errors from all model forms
245		self.model_forms
246			.iter()
247			.flat_map(|model_form| {
248				model_form
249					.form()
250					.errors()
251					.values()
252					.flat_map(|errors| errors.iter().cloned())
253			})
254			.collect()
255	}
256	/// Save all valid forms to the database
257	///
258	/// # Examples
259	///
260	/// ```ignore
261	/// use reinhardt_forms::{ModelFormSet, ModelFormSetConfig};
262	///
263	/// let config = ModelFormSetConfig::new();
264	/// let mut formset = ModelFormSet::<MyModel>::empty("formset".to_string(), config);
265	/// let result = formset.save();
266	/// ```
267	pub fn save(&mut self) -> Result<Vec<T>, FormError> {
268		if !self.is_valid() {
269			return Err(FormError::Validation("Formset is not valid".to_string()));
270		}
271
272		let mut saved_instances = Vec::new();
273
274		// Iterate through each ModelForm and save if it has changes
275		for model_form in &mut self.model_forms {
276			// Check if form has an instance (skip empty forms)
277			if model_form.instance().is_some() {
278				// Save the instance
279				let instance = model_form.save()?;
280				saved_instances.push(instance);
281			}
282		}
283
284		Ok(saved_instances)
285	}
286	/// Get management form data for HTML rendering
287	///
288	/// # Examples
289	///
290	/// ```ignore
291	/// use reinhardt_forms::{ModelFormSet, ModelFormSetConfig};
292	///
293	/// let config = ModelFormSetConfig::new().with_extra(2);
294	/// let formset = ModelFormSet::<MyModel>::empty("article".to_string(), config);
295	/// let mgmt_data = formset.management_form_data();
296	///
297	/// assert!(mgmt_data.contains_key("article-TOTAL_FORMS"));
298	/// assert_eq!(mgmt_data.get("article-TOTAL_FORMS"), Some(&"2".to_string()));
299	/// ```
300	pub fn management_form_data(&self) -> HashMap<String, String> {
301		self.formset.management_form_data()
302	}
303}
304
305/// Builder for creating ModelFormSet instances
306pub struct ModelFormSetBuilder<T: FormModel> {
307	config: ModelFormSetConfig,
308	_phantom: PhantomData<T>,
309}
310
311impl<T: FormModel> ModelFormSetBuilder<T> {
312	/// Create a new builder
313	///
314	/// # Examples
315	///
316	/// ```ignore
317	/// use reinhardt_forms::ModelFormSetBuilder;
318	///
319	/// let builder = ModelFormSetBuilder::<MyModel>::new();
320	/// ```
321	pub fn new() -> Self {
322		Self {
323			config: ModelFormSetConfig::default(),
324			_phantom: PhantomData,
325		}
326	}
327	/// Set the number of extra forms
328	///
329	/// # Examples
330	///
331	/// ```ignore
332	/// use reinhardt_forms::ModelFormSetBuilder;
333	///
334	/// let builder = ModelFormSetBuilder::<MyModel>::new().extra(5);
335	/// ```
336	pub fn extra(mut self, extra: usize) -> Self {
337		self.config.extra = extra;
338		self
339	}
340	/// Enable deletion
341	///
342	/// # Examples
343	///
344	/// ```ignore
345	/// use reinhardt_forms::ModelFormSetBuilder;
346	///
347	/// let builder = ModelFormSetBuilder::<MyModel>::new().can_delete(true);
348	/// ```
349	pub fn can_delete(mut self, can_delete: bool) -> Self {
350		self.config.can_delete = can_delete;
351		self
352	}
353	/// Enable ordering
354	///
355	/// # Examples
356	///
357	/// ```ignore
358	/// use reinhardt_forms::ModelFormSetBuilder;
359	///
360	/// let builder = ModelFormSetBuilder::<MyModel>::new().can_order(true);
361	/// ```
362	pub fn can_order(mut self, can_order: bool) -> Self {
363		self.config.can_order = can_order;
364		self
365	}
366	/// Set maximum number of forms
367	///
368	/// # Examples
369	///
370	/// ```ignore
371	/// use reinhardt_forms::ModelFormSetBuilder;
372	///
373	/// let builder = ModelFormSetBuilder::<MyModel>::new().max_num(10);
374	/// ```
375	pub fn max_num(mut self, max_num: usize) -> Self {
376		self.config.max_num = Some(max_num);
377		self
378	}
379	/// Set minimum number of forms
380	///
381	/// # Examples
382	///
383	/// ```ignore
384	/// use reinhardt_forms::ModelFormSetBuilder;
385	///
386	/// let builder = ModelFormSetBuilder::<MyModel>::new().min_num(1);
387	/// ```
388	pub fn min_num(mut self, min_num: usize) -> Self {
389		self.config.min_num = min_num;
390		self
391	}
392	/// Build the formset with instances
393	///
394	/// # Examples
395	///
396	/// ```ignore
397	/// use reinhardt_forms::ModelFormSetBuilder;
398	///
399	/// let instances = vec![]; // Empty list of model instances
400	/// let builder = ModelFormSetBuilder::<MyModel>::new();
401	/// let formset = builder.build("formset".to_string(), instances);
402	/// ```
403	pub fn build(self, prefix: String, instances: Vec<T>) -> ModelFormSet<T> {
404		ModelFormSet::new(prefix, instances, self.config)
405	}
406	/// Build an empty formset
407	///
408	/// # Examples
409	///
410	/// ```ignore
411	/// use reinhardt_forms::ModelFormSetBuilder;
412	///
413	/// let builder = ModelFormSetBuilder::<MyModel>::new().extra(3);
414	/// let formset = builder.build_empty("formset".to_string());
415	/// ```
416	pub fn build_empty(self, prefix: String) -> ModelFormSet<T> {
417		ModelFormSet::empty(prefix, self.config)
418	}
419}
420
421impl<T: FormModel> Default for ModelFormSetBuilder<T> {
422	fn default() -> Self {
423		Self::new()
424	}
425}
426
427#[cfg(test)]
428mod tests {
429	use super::*;
430	use serde_json::Value;
431
432	// Mock model for testing
433	struct Article {
434		id: i32,
435		title: String,
436		content: String,
437	}
438
439	impl FormModel for Article {
440		fn field_names() -> Vec<String> {
441			vec!["id".to_string(), "title".to_string(), "content".to_string()]
442		}
443
444		fn get_field(&self, name: &str) -> Option<Value> {
445			match name {
446				"id" => Some(Value::Number(self.id.into())),
447				"title" => Some(Value::String(self.title.clone())),
448				"content" => Some(Value::String(self.content.clone())),
449				_ => None,
450			}
451		}
452
453		fn set_field(&mut self, name: &str, value: Value) -> Result<(), String> {
454			match name {
455				"id" => {
456					if let Value::Number(n) = value {
457						self.id = n.as_i64().unwrap() as i32;
458						Ok(())
459					} else {
460						Err("Invalid type for id".to_string())
461					}
462				}
463				"title" => {
464					if let Value::String(s) = value {
465						self.title = s;
466						Ok(())
467					} else {
468						Err("Invalid type for title".to_string())
469					}
470				}
471				"content" => {
472					if let Value::String(s) = value {
473						self.content = s;
474						Ok(())
475					} else {
476						Err("Invalid type for content".to_string())
477					}
478				}
479				_ => Err(format!("Unknown field: {}", name)),
480			}
481		}
482
483		fn save(&mut self) -> Result<(), String> {
484			// Mock save
485			Ok(())
486		}
487	}
488
489	#[test]
490	fn test_model_formset_config() {
491		let config = ModelFormSetConfig::new()
492			.with_extra(3)
493			.with_can_delete(true)
494			.with_max_num(Some(10))
495			.with_min_num(1);
496
497		assert_eq!(config.extra, 3);
498		assert!(config.can_delete);
499		assert_eq!(config.max_num, Some(10));
500		assert_eq!(config.min_num, 1);
501	}
502
503	#[test]
504	fn test_model_formset_empty() {
505		let config = ModelFormSetConfig::new().with_extra(2);
506		let formset = ModelFormSet::<Article>::empty("article".to_string(), config);
507
508		assert_eq!(formset.prefix(), "article");
509		assert_eq!(formset.instances().len(), 0);
510		assert_eq!(formset.total_form_count(), 2);
511	}
512
513	#[test]
514	fn test_model_formset_with_instances() {
515		let instances = vec![
516			Article {
517				id: 1,
518				title: "First Article".to_string(),
519				content: "Content 1".to_string(),
520			},
521			Article {
522				id: 2,
523				title: "Second Article".to_string(),
524				content: "Content 2".to_string(),
525			},
526		];
527
528		let config = ModelFormSetConfig::new();
529		let formset = ModelFormSet::new("article".to_string(), instances, config);
530
531		assert_eq!(formset.instances().len(), 2);
532		assert_eq!(formset.form_count(), 2);
533	}
534
535	#[test]
536	fn test_model_formset_builder() {
537		let formset = ModelFormSetBuilder::<Article>::new()
538			.extra(3)
539			.can_delete(true)
540			.max_num(5)
541			.build_empty("article".to_string());
542
543		assert_eq!(formset.total_form_count(), 3);
544	}
545
546	#[test]
547	fn test_model_formset_management_data() {
548		let config = ModelFormSetConfig::new().with_extra(2).with_min_num(1);
549		let formset = ModelFormSet::<Article>::empty("article".to_string(), config);
550
551		let mgmt_data = formset.management_form_data();
552
553		assert_eq!(mgmt_data.get("article-TOTAL_FORMS"), Some(&"2".to_string()));
554		assert_eq!(
555			mgmt_data.get("article-INITIAL_FORMS"),
556			Some(&"0".to_string())
557		);
558		assert_eq!(
559			mgmt_data.get("article-MIN_NUM_FORMS"),
560			Some(&"1".to_string())
561		);
562	}
563}