2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-28 05:26:47 +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
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();
}
});
}

View File

@ -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,22 +985,50 @@ 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);
if (!response.isValid()) {
@ -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(

View File

@ -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 {