diff --git a/src/components/react-admin/CustomToolbar.js b/src/components/react-admin/CustomToolbar.js
index b2691deb2..163512495 100644
--- a/src/components/react-admin/CustomToolbar.js
+++ b/src/components/react-admin/CustomToolbar.js
@@ -58,7 +58,7 @@ const CustomToolbar = ({
);
diff --git a/src/components/react-admin/ServerSideSimpleForm.js b/src/components/react-admin/ServerSideSimpleForm.js
new file mode 100644
index 000000000..0066ca2a1
--- /dev/null
+++ b/src/components/react-admin/ServerSideSimpleForm.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import { SimpleForm } from 'react-admin';
+import useSaveWithErrorHandling from '../../hooks/useSaveWithErrorHandling';
+
+function ServerSideSimpleForm ({ children, record, resource, ...props }) {
+ const save = useSaveWithErrorHandling(record.id !== undefined, resource);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export default ServerSideSimpleForm;
diff --git a/src/components/react-admin/ServerSideTabbedForm.js b/src/components/react-admin/ServerSideTabbedForm.js
new file mode 100644
index 000000000..27446376f
--- /dev/null
+++ b/src/components/react-admin/ServerSideTabbedForm.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import { TabbedForm } from 'react-admin';
+import useSaveWithErrorHandling from '../../hooks/useSaveWithErrorHandling';
+
+function ServerSideTabbedForm ({ children, record, resource, ...props }) {
+ const save = useSaveWithErrorHandling(record.id !== undefined, resource);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export default ServerSideTabbedForm;
diff --git a/src/hooks/useSaveWithErrorHandling.js b/src/hooks/useSaveWithErrorHandling.js
new file mode 100644
index 000000000..2ccd0423b
--- /dev/null
+++ b/src/hooks/useSaveWithErrorHandling.js
@@ -0,0 +1,41 @@
+import { useMutation, useNotify, useRedirect } from 'ra-core';
+import { useCallback } from 'react';
+
+/**
+ * Convert the django error response to an object react-admin understands
+ *
+ * See https://marmelab.com/react-admin/doc/3.19/CreateEdit.html#server-side-validation
+ */
+function useSaveWithErrorHandling (edit, resource) {
+ const [mutate] = useMutation();
+ const notify = useNotify();
+ const redirect = useRedirect();
+
+ const save = useCallback(
+ async values => {
+ try {
+ await mutate({
+ type: edit ? 'update' : 'create',
+ resource,
+ payload: { id: values.id, data: values },
+ }, { returnPromise: true });
+ redirect(`/${resource}`);
+ } catch (error) {
+ const errorData = error.body ?? error.data;
+ if (error.status === 400 && errorData) {
+ const errorObject = { ...errorData };
+ Object.entries(errorObject).forEach(([key, value]) => {
+ errorObject[key] = value.reduce((acc, current) => `${acc} ${current}`);
+ });
+ notify('ra.message.invalid_form', { type: 'error' });
+ return errorObject;
+ }
+ }
+ return null;
+ },
+ [mutate, edit, resource, notify, redirect],
+ );
+ return save;
+}
+
+export default useSaveWithErrorHandling;
diff --git a/src/modules/RA/BaseLayer/components/BaseLayerForm.js b/src/modules/RA/BaseLayer/components/BaseLayerForm.js
index ac61295d7..43ab22cf1 100644
--- a/src/modules/RA/BaseLayer/components/BaseLayerForm.js
+++ b/src/modules/RA/BaseLayer/components/BaseLayerForm.js
@@ -9,7 +9,6 @@ import {
required,
NumberInput,
SelectInput,
- SimpleForm,
TextInput,
SimpleFormIterator,
translate,
@@ -21,6 +20,7 @@ import ZoomInput from '../../../../components/react-admin/ZoomInput';
import FieldGroup from '../../../../components/react-admin/FieldGroup';
import compose from '../../../../utils/compose';
+import ServerSideSimpleForm from '../../../../components/react-admin/ServerSideSimpleForm';
const styles = {
inline: {
@@ -93,7 +93,7 @@ const BaseLayerForm = ({ basePath, classes, translate: t, ...props }) => {
}, [t, validateURL]);
return (
-
+
{
)}
-
+
);
};
export default compose(
diff --git a/src/modules/RA/BaseLayer/views/Edit.js b/src/modules/RA/BaseLayer/views/Edit.js
index 4b74f16f9..77f3f14c5 100644
--- a/src/modules/RA/BaseLayer/views/Edit.js
+++ b/src/modules/RA/BaseLayer/views/Edit.js
@@ -6,7 +6,7 @@ import DefaultActions from '../../../../components/react-admin/DefaultActions';
export const BaseLayerEdit = props => (
}
>
diff --git a/src/modules/RA/Campaign/components/CampaignFields.js b/src/modules/RA/Campaign/components/CampaignFields.js
index 34b575466..459e63e22 100644
--- a/src/modules/RA/Campaign/components/CampaignFields.js
+++ b/src/modules/RA/Campaign/components/CampaignFields.js
@@ -5,7 +5,6 @@ import {
FormTab,
ReferenceInput,
SelectInput,
- TabbedForm,
TextInput,
} from 'react-admin';
@@ -23,6 +22,7 @@ import CampaignState from './CampaignState';
import UserNameField from '../../User/components/UserNameField';
import StatsBar from './StatsBar';
+import ServerSideTabbedForm from '../../../../components/react-admin/ServerSideTabbedForm';
const useStyles = makeStyles({
inline: {
@@ -47,7 +47,7 @@ const CampaignFields = ({ edit, location, ...props }) => {
const isEditable = edit && state !== 'draft';
return (
- }
initialValues={{ viewpoints: [], state: 'draft' }}
@@ -95,7 +95,7 @@ const CampaignFields = ({ edit, location, ...props }) => {
-
+
);
};
diff --git a/src/modules/RA/Campaign/views/Edit.js b/src/modules/RA/Campaign/views/Edit.js
index 228917d8d..ab4dec856 100644
--- a/src/modules/RA/Campaign/views/Edit.js
+++ b/src/modules/RA/Campaign/views/Edit.js
@@ -15,10 +15,10 @@ export const CampaignEdit = ({ staticContext, ...props }) => {
return (
}
>
-
+
);
};
diff --git a/src/modules/RA/DataLayer/components/DataLayerForm.js b/src/modules/RA/DataLayer/components/DataLayerForm.js
index 62b40bea7..fab4aad1a 100644
--- a/src/modules/RA/DataLayer/components/DataLayerForm.js
+++ b/src/modules/RA/DataLayer/components/DataLayerForm.js
@@ -1,6 +1,6 @@
import React from 'react';
-import { ArrayInput, FormTab, SimpleFormIterator, TabbedForm, TabbedFormTabs } from 'react-admin';
+import { ArrayInput, FormTab, SimpleFormIterator, TabbedFormTabs } from 'react-admin';
import CustomFormTab from '../../../../components/react-admin/CustomFormTab';
@@ -15,6 +15,7 @@ import ReportingTab from './tabs/ReportingTab';
import StyleTab from './tabs/StyleTab';
import TableTab from './tabs/TableTab';
import WidgetsTab from './tabs/WidgetsTab/WidgetsTab';
+import ServerSideTabbedForm from '../../../../components/react-admin/ServerSideTabbedForm';
const initialErrorState = {
definition: false,
@@ -154,7 +155,7 @@ const DataLayerForm = React.memo(props => {
}, []);
return (
- }
sanitizeEmptyValues={false}
{...props}
@@ -258,7 +259,7 @@ const DataLayerForm = React.memo(props => {
>
-
+
);
});
diff --git a/src/modules/RA/DataLayer/views/Edit.js b/src/modules/RA/DataLayer/views/Edit.js
index 8f7a3d8eb..5b90d484c 100644
--- a/src/modules/RA/DataLayer/views/Edit.js
+++ b/src/modules/RA/DataLayer/views/Edit.js
@@ -9,7 +9,7 @@ const EditDataLayerForm = PreventPartialData('description', DataLayerForm);
export const DataLayerEdit = props => (
}
>
diff --git a/src/modules/RA/DataSource/components/DataSourceReadOnlyForm.js b/src/modules/RA/DataSource/components/DataSourceReadOnlyForm.js
index 3cf7abd98..f65848f23 100644
--- a/src/modules/RA/DataSource/components/DataSourceReadOnlyForm.js
+++ b/src/modules/RA/DataSource/components/DataSourceReadOnlyForm.js
@@ -2,14 +2,14 @@ import React from 'react';
import {
TextInput,
Labeled,
- SimpleForm,
translate,
} from 'react-admin';
import DataSourceMainFields from './DataSourceMainFields';
+import ServerSideSimpleForm from '../../../../components/react-admin/ServerSideSimpleForm';
const DataSourceReadOnlyForm = ({ translate: t, ...props }) => (
-
+
@@ -22,7 +22,7 @@ const DataSourceReadOnlyForm = ({ translate: t, ...props }) => (
helperText={t('datasource.form.uid-field-help')}
fullWidth
/>
-
+
);
export default translate(DataSourceReadOnlyForm);
diff --git a/src/modules/RA/DataSource/components/DataSourceTabbedForm.js b/src/modules/RA/DataSource/components/DataSourceTabbedForm.js
index e4b515786..43d4ff554 100644
--- a/src/modules/RA/DataSource/components/DataSourceTabbedForm.js
+++ b/src/modules/RA/DataSource/components/DataSourceTabbedForm.js
@@ -1,6 +1,5 @@
import React from 'react';
import {
- TabbedForm,
TextInput,
BooleanInput,
SelectInput,
@@ -18,6 +17,7 @@ import { fieldTypeChoices } from '..';
import MainTab from './MainTab';
import ReportTab from './ReportTab';
import GeneralInfoTab from './GeneralInfoTab';
+import ServerSideTabbedForm from '../../../../components/react-admin/ServerSideTabbedForm';
const DataSourceTabbedForm = ({ translate: t, ...props }) => {
@@ -25,7 +25,7 @@ const DataSourceTabbedForm = ({ translate: t, ...props }) => {
// report is null when a source has been created and no refresh has been done
const errors = report?.errors ?? [];
return (
-
+
@@ -59,7 +59,7 @@ const DataSourceTabbedForm = ({ translate: t, ...props }) => {
-
+
);
};
diff --git a/src/modules/RA/DataSource/views/Edit.js b/src/modules/RA/DataSource/views/Edit.js
index 8c011b060..51de7115a 100644
--- a/src/modules/RA/DataSource/views/Edit.js
+++ b/src/modules/RA/DataSource/views/Edit.js
@@ -17,7 +17,7 @@ export const DataSourceEdit = props => {
return (
}
{...props}
>
diff --git a/src/modules/RA/Picture/components/CustomToolbar.js b/src/modules/RA/Picture/components/CustomToolbar.js
index 0cde98e6b..09086f098 100644
--- a/src/modules/RA/Picture/components/CustomToolbar.js
+++ b/src/modules/RA/Picture/components/CustomToolbar.js
@@ -127,7 +127,7 @@ const CustomToolbar = ({ basePath, redirect, ...props }) => {
{
const { record: { state } } = props;
return (
- } initialValues={{ state: 'draft' }}>
+ } initialValues={{ state: 'draft' }}>
@@ -167,7 +167,7 @@ const PictureFields = ({ edit, mapConfig, location, ...props }) => {
style={{ width: '50%' }}
/>
-
+
);
};
diff --git a/src/modules/RA/Picture/views/Edit.js b/src/modules/RA/Picture/views/Edit.js
index 3f4fab608..31c596d73 100644
--- a/src/modules/RA/Picture/views/Edit.js
+++ b/src/modules/RA/Picture/views/Edit.js
@@ -14,10 +14,10 @@ export const PictureEdit = ({ staticContext, ...props }) => {
return (
}
>
-
+
);
};
diff --git a/src/modules/RA/Scene/components/SceneForm.js b/src/modules/RA/Scene/components/SceneForm.js
index 6e816779c..990ffca10 100644
--- a/src/modules/RA/Scene/components/SceneForm.js
+++ b/src/modules/RA/Scene/components/SceneForm.js
@@ -10,7 +10,6 @@ import {
ImageField,
NumberInput,
SelectInput,
- SimpleForm,
TextInput,
FormDataConsumer,
required,
@@ -27,6 +26,7 @@ import SceneFormNameField from './SceneFormNameField';
import { RES_BASELAYER } from '../../ra-modules';
import MapExtentInput from './MapExtentInput';
+import ServerSideSimpleForm from '../../../../components/react-admin/ServerSideSimpleForm';
const styles = {
inline: {
@@ -51,14 +51,14 @@ const ReportField = ({ record, source, className, label, ...rest }) => {
);
};
-const SceneForm = ({ edit = false, translate: t, classes, ...props }) => {
+const SceneForm = ({ translate: t, classes, ...props }) => {
const { record } = props;
-
+ const edit = record.id !== undefined;
/* sanitizeEmptyValues is false for this form to prevent
* this issue with the layer tree https://github.com/marmelab/react-admin/issues/5427
*/
return (
-
+
{edit && }
{isObjectEmpty(record) && (
@@ -111,7 +111,7 @@ const SceneForm = ({ edit = false, translate: t, classes, ...props }) => {
()}
-
+
);
};
diff --git a/src/modules/RA/Scene/views/Edit.js b/src/modules/RA/Scene/views/Edit.js
index 90a6ea1fa..ef9b183d1 100644
--- a/src/modules/RA/Scene/views/Edit.js
+++ b/src/modules/RA/Scene/views/Edit.js
@@ -9,12 +9,12 @@ const EditSceneForm = PreventPartialData('config', SceneForm);
export const SceneEdit = props => (
}
>
-
+
);
diff --git a/src/modules/RA/User/components/UserFields.js b/src/modules/RA/User/components/UserFields.js
index b72c1c573..929557be9 100644
--- a/src/modules/RA/User/components/UserFields.js
+++ b/src/modules/RA/User/components/UserFields.js
@@ -1,7 +1,6 @@
import React from 'react';
import {
TextInput,
- SimpleForm,
BooleanInput,
ReferenceArrayInput,
SelectArrayInput,
@@ -22,6 +21,7 @@ import UserFieldsHelp from './UserFieldsHelp';
import { RES_USERGROUP } from '../../ra-modules';
import UserPasswordInput from './UserPasswordInput';
+import ServerSideSimpleForm from '../../../../components/react-admin/ServerSideSimpleForm';
const useStyles = makeStyles({
inline: {
@@ -43,7 +43,7 @@ const UserFields = ({ user: { is_superuser: isSuperUser } = {}, ...props }) => {
};
return (
-
+
{
>
-
+
);
};
diff --git a/src/modules/RA/User/views/Edit.js b/src/modules/RA/User/views/Edit.js
index 7f932991d..32fdc888a 100644
--- a/src/modules/RA/User/views/Edit.js
+++ b/src/modules/RA/User/views/Edit.js
@@ -10,7 +10,7 @@ export const UserEdit = props => (
}
- mutationMode="optimistic"
+ mutationMode="pessimistic"
actions={}
>
diff --git a/src/modules/RA/UserGroup/components/UserGroupFields.js b/src/modules/RA/UserGroup/components/UserGroupFields.js
index 6a14cf13f..da57ffc57 100644
--- a/src/modules/RA/UserGroup/components/UserGroupFields.js
+++ b/src/modules/RA/UserGroup/components/UserGroupFields.js
@@ -2,28 +2,31 @@ import React from 'react';
import {
TextInput,
- SimpleForm,
ReferenceArrayInput,
SelectArrayInput,
} from 'react-admin';
import UserNameField from '../../User/components/UserNameField';
import { RES_USER, RES_PERMISSION } from '../../ra-modules';
+import ServerSideSimpleForm from '../../../../components/react-admin/ServerSideSimpleForm';
-const UserGroupFields = ({ edit = false, ...props }) => (
-
- {edit && }
+const UserGroupFields = ({ record, ...props }) => {
+ const edit = record.id !== undefined;
+ return (
+
+ {edit && }
-
+
-
- UserNameField({ record })} />
-
+
+ UserNameField({ record: r })} />
+
-
-
-
-
-);
+
+
+
+
+ );
+};
export default UserGroupFields;
diff --git a/src/modules/RA/UserGroup/views/Edit.js b/src/modules/RA/UserGroup/views/Edit.js
index b667f63a2..91c82437a 100644
--- a/src/modules/RA/UserGroup/views/Edit.js
+++ b/src/modules/RA/UserGroup/views/Edit.js
@@ -7,10 +7,10 @@ import DefaultActions from '../../../../components/react-admin/DefaultActions';
export const UserEdit = props => (
}
>
-
+
);
diff --git a/src/modules/RA/Viewpoint/components/ViewpointFields.js b/src/modules/RA/Viewpoint/components/ViewpointFields.js
index f062fd0f5..9f931f149 100644
--- a/src/modules/RA/Viewpoint/components/ViewpointFields.js
+++ b/src/modules/RA/Viewpoint/components/ViewpointFields.js
@@ -23,7 +23,6 @@ import {
ReferenceArrayField,
required,
SimpleFormIterator,
- TabbedForm,
TextField,
SelectInput,
TextInput,
@@ -45,6 +44,7 @@ import { useGetListAllPages } from '../../../../utils/react-admin/hooks';
import useAppSettings from '../../../../hooks/useAppSettings';
import themeRenderer from './themeRenderer';
+import ServerSideTabbedForm from '../../../../components/react-admin/ServerSideTabbedForm';
const styles = {
inline: {
@@ -162,7 +162,7 @@ const ViewpointFields = ({ translate: t, edit, classes, mapConfig, record, ...pr
}, [getRemoteData]);
return (
-
)}
-
+
);
};
diff --git a/src/modules/RA/Viewpoint/views/Edit.js b/src/modules/RA/Viewpoint/views/Edit.js
index c8cbd4bd1..c85ea4fd3 100644
--- a/src/modules/RA/Viewpoint/views/Edit.js
+++ b/src/modules/RA/Viewpoint/views/Edit.js
@@ -7,10 +7,10 @@ import DefaultActions from '../../../../components/react-admin/DefaultActions';
export const ViewpointEdit = props => (
}
>
-
+
);
diff --git a/src/modules/RA/ViewpointCity/components/CityFields.js b/src/modules/RA/ViewpointCity/components/CityFields.js
index 5cd68b37f..2210307a2 100644
--- a/src/modules/RA/ViewpointCity/components/CityFields.js
+++ b/src/modules/RA/ViewpointCity/components/CityFields.js
@@ -1,17 +1,17 @@
import React from 'react';
import {
TextInput,
- SimpleForm,
} from 'react-admin';
import { connectAuthProvider } from '@terralego/core/modules/Auth';
+import ServerSideSimpleForm from '../../../../components/react-admin/ServerSideSimpleForm';
const UserFields = props => (
-
+
-
+
);
export default connectAuthProvider('user')(UserFields);
diff --git a/src/modules/RA/ViewpointCity/views/Edit.js b/src/modules/RA/ViewpointCity/views/Edit.js
index 42f8e46e6..54dbfeb3b 100644
--- a/src/modules/RA/ViewpointCity/views/Edit.js
+++ b/src/modules/RA/ViewpointCity/views/Edit.js
@@ -7,10 +7,10 @@ import DefaultActions from '../../../../components/react-admin/DefaultActions';
export const ViewpointEdit = props => (
}
>
-
+
);
diff --git a/src/modules/RA/ViewpointTheme/components/ThemeFields.js b/src/modules/RA/ViewpointTheme/components/ThemeFields.js
index 34c501194..d1dc66a08 100644
--- a/src/modules/RA/ViewpointTheme/components/ThemeFields.js
+++ b/src/modules/RA/ViewpointTheme/components/ThemeFields.js
@@ -2,12 +2,12 @@ import React from 'react';
import {
TextInput,
SelectInput,
- SimpleForm,
} from 'react-admin';
import { connectAuthProvider } from '@terralego/core/modules/Auth';
import useAppSettings from '../../../../hooks/useAppSettings';
import optionRenderer from './categoryRenderer';
+import ServerSideSimpleForm from '../../../../components/react-admin/ServerSideSimpleForm';
const UserFields = props => {
const { modules:
@@ -17,7 +17,7 @@ const UserFields = props => {
} = {},
} = {} } = useAppSettings();
return (
-
+
@@ -26,7 +26,7 @@ const UserFields = props => {
choices={themeCategories}
optionText={optionRenderer}
/>
-
+
);
};
diff --git a/src/modules/RA/ViewpointTheme/views/Edit.js b/src/modules/RA/ViewpointTheme/views/Edit.js
index 80f3d96ab..63403b017 100644
--- a/src/modules/RA/ViewpointTheme/views/Edit.js
+++ b/src/modules/RA/ViewpointTheme/views/Edit.js
@@ -7,10 +7,10 @@ import DefaultActions from '../../../../components/react-admin/DefaultActions';
export const ViewpointEdit = props => (
}
>
-
+
);
diff --git a/src/services/react-admin/dataProvider.js b/src/services/react-admin/dataProvider.js
index 9f40aa670..a380fea26 100644
--- a/src/services/react-admin/dataProvider.js
+++ b/src/services/react-admin/dataProvider.js
@@ -10,6 +10,11 @@ const httpClient = (url, options = {}) =>
token: `JWT ${auth.getToken()}`,
},
...options,
+ }).catch(e => {
+ if (e.body !== undefined) {
+ e.message = `Error: ${JSON.stringify(e.body)}`;
+ }
+ throw e;
});
export default (...args) => drfProvider(Api.host, httpClient)(...args);