mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 13:36:50 +00:00
Handle form posting with complex "layered" data
- Handle data rendering - Handle returned errors
This commit is contained in:
parent
80b203ce7b
commit
b7f9f1c55f
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,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(
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user