2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-28 21:46:46 +00:00

Handle form posting with complex "layered" data

- Handle data rendering
- Handle returned errors
This commit is contained in:
Oliver 2021-10-03 00:40:26 +10:00
parent 80b203ce7b
commit b7f9f1c55f
3 changed files with 88 additions and 17 deletions

View File

@ -485,11 +485,9 @@ class InvenTreeAPI {
// Perform a PATCH request // Perform a PATCH request
Future<APIResponse> patch(String url, {Map<String, String> body = const {}, int? expectedStatusCode}) async { Future<APIResponse> patch(String url, {Map<String, dynamic> body = const {}, int? expectedStatusCode}) async {
Map<String, String> _body = {};
// Copy across provided data Map<String, dynamic> _body = body;
body.forEach((K, V) => _body[K] = V);
HttpClientRequest? request = await apiRequest(url, "PATCH"); HttpClientRequest? request = await apiRequest(url, "PATCH");
@ -599,7 +597,7 @@ class InvenTreeAPI {
* Upload a file to the given URL * Upload a file to the given URL
*/ */
Future<APIResponse> uploadFile(String url, File f, 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 _url = makeApiUrl(url);
var request = http.MultipartRequest(method, Uri.parse(_url)); var request = http.MultipartRequest(method, Uri.parse(_url));
@ -607,8 +605,13 @@ class InvenTreeAPI {
request.headers.addAll(defaultHeaders()); request.headers.addAll(defaultHeaders());
if (fields != null) { if (fields != null) {
fields.forEach((String key, String value) { fields.forEach((String key, dynamic value) {
request.fields[key] = value;
if (value == null) {
request.fields[key] = "";
} else {
request.fields[key] = value.toString();
}
}); });
} }

View File

@ -101,13 +101,24 @@ class APIFormField {
// Note: This parameter is only defined locally // Note: This parameter is only defined locally
String get parent => (data["parent"] ?? "") as String; String get parent => (data["parent"] ?? "") as String;
bool get isSimple => !nested && parent.isEmpty;
// Is this field read only? // Is this field read only?
bool get readOnly => (getParameter("read_only") ?? false) as bool; bool get readOnly => (getParameter("read_only") ?? false) as bool;
bool get multiline => (getParameter("multiline") ?? false) as bool; bool get multiline => (getParameter("multiline") ?? false) as bool;
// Get the "value" as a string (look for "default" if not available) // 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 // Get the "default" as a string
dynamic get defaultValue => getParameter("default"); dynamic get defaultValue => getParameter("default");
@ -166,6 +177,28 @@ class APIFormField {
bool hasErrors() => errorMessages().isNotEmpty; 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 // Return the error message associated with this field
List<String> errorMessages() { List<String> errorMessages() {
List<dynamic> errors = (data["errors"] ?? []) as List<dynamic>; List<dynamic> errors = (data["errors"] ?? []) as List<dynamic>;
@ -902,8 +935,7 @@ class _APIFormWidgetState extends State<APIFormWidget> {
return widgets; 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 a file upload is required, we have to handle the submission differently
if (fileField.isNotEmpty) { 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 { Future<void> _save(BuildContext context) async {
// Package up the form data // 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) { for (var field in fields) {
dynamic value = field.value; if (field.isSimple) {
// Simple top-level field data
if (value == null) { data[field.name] = field.renderToString();
data[field.name] = "";
} else { } 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); final response = await _submit(data);
@ -976,6 +1036,9 @@ class _APIFormWidgetState extends State<APIFormWidget> {
return; return;
} }
print("Response: ${response.statusCode}");
print(response.data.toString());
switch (response.statusCode) { switch (response.statusCode) {
case 200: case 200:
case 201: case 201:
@ -1010,10 +1073,13 @@ class _APIFormWidgetState extends State<APIFormWidget> {
success: false success: false
); );
print(response.data);
// Update field errors // Update field errors
for (var field in fields) { for (var field in fields) {
field.data["errors"] = response.data[field.name]; field.extractErrorMessages(response);
} }
break; break;
case 401: case 401:
showSnackIcon( showSnackIcon(

View File

@ -85,6 +85,8 @@ class InvenTreePurchaseOrder extends InvenTreeModel {
bool get isOpen => status == PO_STATUS_PENDING || status == PO_STATUS_PLACED; 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; bool get isFailed => status == PO_STATUS_CANCELLED || status == PO_STATUS_LOST || status == PO_STATUS_RETURNED;
Future<List<InvenTreePOLineItem>> getLineItems() async { Future<List<InvenTreePOLineItem>> getLineItems() async {