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:
parent
80b203ce7b
commit
b7f9f1c55f
17
lib/api.dart
17
lib/api.dart
@ -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();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user