mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-30 21:05:42 +00:00 
			
		
		
		
	Handle form posting with complex "layered" data
- Handle data rendering - Handle returned errors
This commit is contained in:
		
							
								
								
									
										17
									
								
								lib/api.dart
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								lib/api.dart
									
									
									
									
									
								
							| @@ -485,11 +485,9 @@ class InvenTreeAPI { | ||||
|  | ||||
|  | ||||
|   // Perform a PATCH request | ||||
|   Future<APIResponse> patch(String url, {Map<String, String> body = const {}, int? expectedStatusCode}) async { | ||||
|     Map<String, String> _body = {}; | ||||
|   Future<APIResponse> patch(String url, {Map<String, dynamic> body = const {}, int? expectedStatusCode}) async { | ||||
|  | ||||
|     // Copy across provided data | ||||
|     body.forEach((K, V) => _body[K] = V); | ||||
|     Map<String, dynamic> _body = body; | ||||
|  | ||||
|     HttpClientRequest? request = await apiRequest(url, "PATCH"); | ||||
|  | ||||
| @@ -599,7 +597,7 @@ class InvenTreeAPI { | ||||
|    * Upload a file to the given URL | ||||
|    */ | ||||
|   Future<APIResponse> uploadFile(String url, File f, | ||||
|       {String name = "attachment", String method="POST", Map<String, String>? fields}) async { | ||||
|       {String name = "attachment", String method="POST", Map<String, dynamic>? fields}) async { | ||||
|     var _url = makeApiUrl(url); | ||||
|  | ||||
|     var request = http.MultipartRequest(method, Uri.parse(_url)); | ||||
| @@ -607,8 +605,13 @@ class InvenTreeAPI { | ||||
|     request.headers.addAll(defaultHeaders()); | ||||
|  | ||||
|     if (fields != null) { | ||||
|       fields.forEach((String key, String value) { | ||||
|         request.fields[key] = value; | ||||
|       fields.forEach((String key, dynamic value) { | ||||
|  | ||||
|         if (value == null) { | ||||
|           request.fields[key] = ""; | ||||
|         } else { | ||||
|           request.fields[key] = value.toString(); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -101,13 +101,24 @@ class APIFormField { | ||||
|   // Note: This parameter is only defined locally | ||||
|   String get parent => (data["parent"] ?? "") as String; | ||||
|  | ||||
|   bool get isSimple => !nested && parent.isEmpty; | ||||
|  | ||||
|   // Is this field read only? | ||||
|   bool get readOnly => (getParameter("read_only") ?? false) as bool; | ||||
|  | ||||
|   bool get multiline => (getParameter("multiline") ?? false) as bool; | ||||
|  | ||||
|   // Get the "value" as a string (look for "default" if not available) | ||||
|   dynamic get value => data["value"] ?? data["instance_value"] ?? data["default"]; | ||||
|   dynamic get value => data["value"] ?? data["instance_value"] ?? defaultValue; | ||||
|  | ||||
|   // Render value to string (for form submission) | ||||
|   String renderToString() { | ||||
|     if (value == null) { | ||||
|       return ""; | ||||
|     } else { | ||||
|       return value.toString(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Get the "default" as a string | ||||
|   dynamic get defaultValue => getParameter("default"); | ||||
| @@ -166,6 +177,28 @@ class APIFormField { | ||||
|  | ||||
|   bool hasErrors() => errorMessages().isNotEmpty; | ||||
|  | ||||
|   // Extract error messages from the server response | ||||
|   void extractErrorMessages(APIResponse response) { | ||||
|  | ||||
|     if (isSimple) { | ||||
|       // Simple fields are easily handled | ||||
|       data["errors"] = response.data[name]; | ||||
|     } else { | ||||
|       if (parent.isNotEmpty) { | ||||
|         dynamic parentElement = response.data[parent]; | ||||
|  | ||||
|         // Extract from list | ||||
|         if (parentElement is List) { | ||||
|           parentElement = parentElement[0]; | ||||
|         } | ||||
|  | ||||
|         if (parentElement is Map) { | ||||
|           data["errors"] = parentElement[name]; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Return the error message associated with this field | ||||
|   List<String> errorMessages() { | ||||
|     List<dynamic> errors = (data["errors"] ?? []) as List<dynamic>; | ||||
| @@ -902,8 +935,7 @@ class _APIFormWidgetState extends State<APIFormWidget> { | ||||
|     return widgets; | ||||
|   } | ||||
|  | ||||
|   Future<APIResponse> _submit(Map<String, String> data) async { | ||||
|  | ||||
|   Future<APIResponse> _submit(Map<String, dynamic> data) async { | ||||
|  | ||||
|     // If a file upload is required, we have to handle the submission differently | ||||
|     if (fileField.isNotEmpty) { | ||||
| @@ -953,21 +985,49 @@ class _APIFormWidgetState extends State<APIFormWidget> { | ||||
|  | ||||
|   } | ||||
|  | ||||
|   /* | ||||
|    * Submit the form data to the server, and handle the results | ||||
|    */ | ||||
|   Future<void> _save(BuildContext context) async { | ||||
|  | ||||
|     // Package up the form data | ||||
|     Map<String, String> data = {}; | ||||
|     Map<String, dynamic> data = {}; | ||||
|  | ||||
|     // Iterate through and find "simple" top-level fields | ||||
|  | ||||
|     for (var field in fields) { | ||||
|  | ||||
|       dynamic value = field.value; | ||||
|  | ||||
|       if (value == null) { | ||||
|         data[field.name] = ""; | ||||
|       if (field.isSimple) { | ||||
|         // Simple top-level field data | ||||
|         data[field.name] = field.renderToString(); | ||||
|       } else { | ||||
|         data[field.name] = value.toString(); | ||||
|         // Not so simple... (WHY DID I MAKE THE API SO COMPLEX?) | ||||
|         if (field.parent.isNotEmpty) { | ||||
|  | ||||
|           // TODO: This is a dirty hack, there *must* be a cleaner way?! | ||||
|  | ||||
|           dynamic parent = data[field.parent] ?? {}; | ||||
|  | ||||
|           // In the case of a "nested" object, we need to extract the first item | ||||
|           if (parent is List) { | ||||
|             parent = parent.first; | ||||
|           } | ||||
|  | ||||
|           parent[field.name] = field.renderToString(); | ||||
|  | ||||
|           // Nested fields must be handled as an array! | ||||
|           // For now, we only allow single length nested fields | ||||
|           if (field.nested) { | ||||
|             parent = [parent]; | ||||
|           } | ||||
|  | ||||
|           data[field.parent] = parent; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     print("Submitting form data to server:"); | ||||
|     print(data.toString()); | ||||
|  | ||||
|     final response = await _submit(data); | ||||
|  | ||||
| @@ -976,6 +1036,9 @@ class _APIFormWidgetState extends State<APIFormWidget> { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     print("Response: ${response.statusCode}"); | ||||
|     print(response.data.toString()); | ||||
|  | ||||
|     switch (response.statusCode) { | ||||
|       case 200: | ||||
|       case 201: | ||||
| @@ -1010,10 +1073,13 @@ class _APIFormWidgetState extends State<APIFormWidget> { | ||||
|           success: false | ||||
|         ); | ||||
|  | ||||
|         print(response.data); | ||||
|  | ||||
|         // Update field errors | ||||
|         for (var field in fields) { | ||||
|           field.data["errors"] = response.data[field.name]; | ||||
|           field.extractErrorMessages(response); | ||||
|         } | ||||
|  | ||||
|         break; | ||||
|       case 401: | ||||
|         showSnackIcon( | ||||
|   | ||||
| @@ -85,6 +85,8 @@ class InvenTreePurchaseOrder extends InvenTreeModel { | ||||
|  | ||||
|   bool get isOpen => status == PO_STATUS_PENDING || status == PO_STATUS_PLACED; | ||||
|  | ||||
|   bool get isPlaced => status == PO_STATUS_PLACED; | ||||
|  | ||||
|   bool get isFailed => status == PO_STATUS_CANCELLED || status == PO_STATUS_LOST || status == PO_STATUS_RETURNED; | ||||
|  | ||||
|   Future<List<InvenTreePOLineItem>> getLineItems() async { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user