diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 58c4865cf2..486e647e87 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -107,6 +107,12 @@ jobs:
matrix:
python-version: ['3.8', '3.12']
db-backend: [mysql, postgres]
+ # test Python 3.13 already
+ # release is scheduled for 2024-10-01 (https://peps.python.org/pep-0719/)
+ # when released: replace "python-version: ['3.8', '3.12']" with python-version: ['3.8', '3.13']
+ include:
+ - python-version: '3.13'
+ db-backend: postgres
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
diff --git a/.gitignore b/.gitignore
index 2af3bf59da..3d2c0d88b1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,8 +36,12 @@ dist
rdmo/management/static
-rdmo/projects/static/projects/js/projects.js
+rdmo/core/static/core/js/base.js
+rdmo/core/static/core/fonts
+rdmo/core/static/core/css/base.css
+
+rdmo/projects/static/projects/js/*.js
rdmo/projects/static/projects/fonts
-rdmo/projects/static/projects/css/projects.css
+rdmo/projects/static/projects/css/*.css
screenshots
diff --git a/rdmo/core/static/core/fonts/DroidSans-Bold.ttf b/rdmo/core/assets/fonts/DroidSans-Bold.ttf
similarity index 100%
rename from rdmo/core/static/core/fonts/DroidSans-Bold.ttf
rename to rdmo/core/assets/fonts/DroidSans-Bold.ttf
diff --git a/rdmo/core/static/core/fonts/DroidSans.ttf b/rdmo/core/assets/fonts/DroidSans.ttf
similarity index 100%
rename from rdmo/core/static/core/fonts/DroidSans.ttf
rename to rdmo/core/assets/fonts/DroidSans.ttf
diff --git a/rdmo/core/static/core/fonts/DroidSansMono.ttf b/rdmo/core/assets/fonts/DroidSansMono.ttf
similarity index 100%
rename from rdmo/core/static/core/fonts/DroidSansMono.ttf
rename to rdmo/core/assets/fonts/DroidSansMono.ttf
diff --git a/rdmo/core/static/core/fonts/DroidSerif-Bold.ttf b/rdmo/core/assets/fonts/DroidSerif-Bold.ttf
similarity index 100%
rename from rdmo/core/static/core/fonts/DroidSerif-Bold.ttf
rename to rdmo/core/assets/fonts/DroidSerif-Bold.ttf
diff --git a/rdmo/core/static/core/fonts/DroidSerif-BoldItalic.ttf b/rdmo/core/assets/fonts/DroidSerif-BoldItalic.ttf
similarity index 100%
rename from rdmo/core/static/core/fonts/DroidSerif-BoldItalic.ttf
rename to rdmo/core/assets/fonts/DroidSerif-BoldItalic.ttf
diff --git a/rdmo/core/static/core/fonts/DroidSerif-Italic.ttf b/rdmo/core/assets/fonts/DroidSerif-Italic.ttf
similarity index 100%
rename from rdmo/core/static/core/fonts/DroidSerif-Italic.ttf
rename to rdmo/core/assets/fonts/DroidSerif-Italic.ttf
diff --git a/rdmo/core/static/core/fonts/DroidSerif.ttf b/rdmo/core/assets/fonts/DroidSerif.ttf
similarity index 100%
rename from rdmo/core/static/core/fonts/DroidSerif.ttf
rename to rdmo/core/assets/fonts/DroidSerif.ttf
diff --git a/rdmo/core/assets/img/favicon.png b/rdmo/core/assets/img/favicon.png
new file mode 100644
index 0000000000..042bcf2bbb
Binary files /dev/null and b/rdmo/core/assets/img/favicon.png differ
diff --git a/rdmo/core/assets/img/rdmo-logo.svg b/rdmo/core/assets/img/rdmo-logo.svg
new file mode 100644
index 0000000000..93fc2eae24
--- /dev/null
+++ b/rdmo/core/assets/img/rdmo-logo.svg
@@ -0,0 +1,260 @@
+
+
+
+
diff --git a/rdmo/core/assets/js/actions/actionTypes.js b/rdmo/core/assets/js/actions/actionTypes.js
new file mode 100644
index 0000000000..1f97c54f3d
--- /dev/null
+++ b/rdmo/core/assets/js/actions/actionTypes.js
@@ -0,0 +1,17 @@
+export const UPDATE_CONFIG = 'UPDATE_CONFIG'
+export const DELETE_CONFIG = 'DELETE_CONFIG'
+
+export const ADD_TO_PENDING = 'ADD_TO_PENDING'
+export const REMOVE_FROM_PENDING = 'REMOVE_FROM_PENDING'
+
+export const FETCH_SETTINGS_ERROR = 'FETCH_SETTINGS_ERROR'
+export const FETCH_SETTINGS_INIT = 'FETCH_SETTINGS_INIT'
+export const FETCH_SETTINGS_SUCCESS = 'FETCH_SETTINGS_SUCCESS'
+
+export const FETCH_TEMPLATES_ERROR = 'FETCH_TEMPLATES_ERROR'
+export const FETCH_TEMPLATES_INIT = 'FETCH_TEMPLATES_INIT'
+export const FETCH_TEMPLATES_SUCCESS = 'FETCH_TEMPLATES_SUCCESS'
+
+export const FETCH_CURRENT_USER_ERROR = 'FETCH_CURRENT_USER_ERROR'
+export const FETCH_CURRENT_USER_INIT = 'FETCH_CURRENT_USER_INIT'
+export const FETCH_CURRENT_USER_SUCCESS = 'FETCH_CURRENT_USER_SUCCESS'
diff --git a/rdmo/core/assets/js/actions/configActions.js b/rdmo/core/assets/js/actions/configActions.js
new file mode 100644
index 0000000000..0aaebe47b9
--- /dev/null
+++ b/rdmo/core/assets/js/actions/configActions.js
@@ -0,0 +1,9 @@
+import { UPDATE_CONFIG, DELETE_CONFIG } from './actionTypes'
+
+export function updateConfig(path, value, ls = false) {
+ return {type: UPDATE_CONFIG, path, value, ls}
+}
+
+export function deleteConfig(path, ls = false) {
+ return {type: DELETE_CONFIG, path, ls}
+}
diff --git a/rdmo/core/assets/js/actions/pendingActions.js b/rdmo/core/assets/js/actions/pendingActions.js
new file mode 100644
index 0000000000..2ee4aaf514
--- /dev/null
+++ b/rdmo/core/assets/js/actions/pendingActions.js
@@ -0,0 +1,9 @@
+import { ADD_TO_PENDING, REMOVE_FROM_PENDING } from './actionTypes'
+
+export function addToPending(item) {
+ return {type: ADD_TO_PENDING, item}
+}
+
+export function removeFromPending(item) {
+ return {type: REMOVE_FROM_PENDING, item}
+}
diff --git a/rdmo/core/assets/js/actions/settingsActions.js b/rdmo/core/assets/js/actions/settingsActions.js
new file mode 100644
index 0000000000..d95f6be36a
--- /dev/null
+++ b/rdmo/core/assets/js/actions/settingsActions.js
@@ -0,0 +1,25 @@
+import CoreApi from '../api/CoreApi'
+
+import { FETCH_SETTINGS_ERROR, FETCH_SETTINGS_INIT, FETCH_SETTINGS_SUCCESS } from './actionTypes'
+
+export function fetchSettings() {
+ return function(dispatch) {
+ dispatch(fetchSettingsInit())
+
+ return CoreApi.fetchSettings()
+ .then((settings) => dispatch(fetchSettingsSuccess(settings)))
+ .catch((errors) => dispatch(fetchSettingsError(errors)))
+ }
+}
+
+export function fetchSettingsInit() {
+ return {type: FETCH_SETTINGS_INIT}
+}
+
+export function fetchSettingsSuccess(settings) {
+ return {type: FETCH_SETTINGS_SUCCESS, settings}
+}
+
+export function fetchSettingsError(errors) {
+ return {type: FETCH_SETTINGS_ERROR, errors}
+}
diff --git a/rdmo/core/assets/js/actions/templateActions.js b/rdmo/core/assets/js/actions/templateActions.js
new file mode 100644
index 0000000000..90e598e342
--- /dev/null
+++ b/rdmo/core/assets/js/actions/templateActions.js
@@ -0,0 +1,25 @@
+import CoreApi from '../api/CoreApi'
+
+import { FETCH_TEMPLATES_ERROR, FETCH_TEMPLATES_INIT, FETCH_TEMPLATES_SUCCESS } from './actionTypes'
+
+export function fetchTemplates() {
+ return function(dispatch) {
+ dispatch(fetchTemplatesInit())
+
+ return CoreApi.fetchTemplates()
+ .then((templates) => dispatch(fetchTemplatesSuccess(templates)))
+ .catch((errors) => dispatch(fetchTemplatesError(errors)))
+ }
+}
+
+export function fetchTemplatesInit() {
+ return {type: FETCH_TEMPLATES_INIT}
+}
+
+export function fetchTemplatesSuccess(templates) {
+ return {type: FETCH_TEMPLATES_SUCCESS, templates}
+}
+
+export function fetchTemplatesError(errors) {
+ return {type: FETCH_TEMPLATES_ERROR, errors}
+}
diff --git a/rdmo/core/assets/js/actions/userActions.js b/rdmo/core/assets/js/actions/userActions.js
new file mode 100644
index 0000000000..2c2922428d
--- /dev/null
+++ b/rdmo/core/assets/js/actions/userActions.js
@@ -0,0 +1,25 @@
+import AccountsApi from '../api/AccountsApi'
+
+import { FETCH_CURRENT_USER_ERROR, FETCH_CURRENT_USER_INIT, FETCH_CURRENT_USER_SUCCESS } from './actionTypes'
+
+export function fetchCurrentUser() {
+ return function(dispatch) {
+ dispatch(fetchCurrentUserInit())
+
+ return AccountsApi.fetchCurrentUser(true)
+ .then(currentUser => dispatch(fetchCurrentUserSuccess({ currentUser })))
+ .catch(error => dispatch(fetchCurrentUserError(error)))
+ }
+}
+
+export function fetchCurrentUserInit() {
+ return {type: FETCH_CURRENT_USER_INIT}
+}
+
+export function fetchCurrentUserSuccess(currentUser) {
+ return {type: FETCH_CURRENT_USER_SUCCESS, currentUser}
+}
+
+export function fetchCurrentUserError(error) {
+ return {type: FETCH_CURRENT_USER_ERROR, error}
+}
diff --git a/rdmo/projects/assets/js/api/AccountsApi.js b/rdmo/core/assets/js/api/AccountsApi.js
similarity index 100%
rename from rdmo/projects/assets/js/api/AccountsApi.js
rename to rdmo/core/assets/js/api/AccountsApi.js
diff --git a/rdmo/core/assets/js/api/BaseApi.js b/rdmo/core/assets/js/api/BaseApi.js
index c2d81e9473..ae0281bb69 100644
--- a/rdmo/core/assets/js/api/BaseApi.js
+++ b/rdmo/core/assets/js/api/BaseApi.js
@@ -1,7 +1,7 @@
import Cookies from 'js-cookie'
import isUndefined from 'lodash/isUndefined'
-import baseUrl from '../utils/baseUrl'
+import { baseUrl } from '../utils/meta'
function ApiError(statusText, status) {
this.status = status
@@ -52,6 +52,28 @@ class BaseApi {
})
}
+ static postFormData(url, formData) {
+ return fetch(baseUrl + url, {
+ method: 'POST',
+ headers: {
+ 'X-CSRFToken': Cookies.get('csrftoken')
+ },
+ body: formData
+ }).catch(error => {
+ throw new ApiError(error.message)
+ }).then(response => {
+ if (response.ok) {
+ return response.json()
+ } else if (response.status == 400) {
+ return response.json().then(errors => {
+ throw new ValidationError(errors)
+ })
+ } else {
+ throw new ApiError(response.statusText, response.status)
+ }
+ })
+ }
+
static put(url, data) {
return fetch(baseUrl + url, {
method: 'PUT',
diff --git a/rdmo/core/assets/js/api/CoreApi.js b/rdmo/core/assets/js/api/CoreApi.js
index ef77eb4701..d97cfc2983 100644
--- a/rdmo/core/assets/js/api/CoreApi.js
+++ b/rdmo/core/assets/js/api/CoreApi.js
@@ -14,6 +14,10 @@ class CoreApi extends BaseApi {
return this.get('/api/v1/core/groups/')
}
+ static fetchTemplates() {
+ return this.get('/api/v1/core/templates/')
+ }
+
}
export default CoreApi
diff --git a/rdmo/core/assets/js/base.js b/rdmo/core/assets/js/base.js
new file mode 100644
index 0000000000..3bab0caba5
--- /dev/null
+++ b/rdmo/core/assets/js/base.js
@@ -0,0 +1 @@
+import 'bootstrap-sass'
diff --git a/rdmo/core/assets/js/components/Html.js b/rdmo/core/assets/js/components/Html.js
new file mode 100644
index 0000000000..acd13bbd23
--- /dev/null
+++ b/rdmo/core/assets/js/components/Html.js
@@ -0,0 +1,16 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { isEmpty } from 'lodash'
+
+const Html = ({ html = '' }) => {
+ return !isEmpty(html) && (
+
+ )
+}
+
+Html.propTypes = {
+ className: PropTypes.string,
+ html: PropTypes.string
+}
+
+export default Html
diff --git a/rdmo/core/assets/js/components/Modal.js b/rdmo/core/assets/js/components/Modal.js
index a3f2cd70b8..22c07abeab 100644
--- a/rdmo/core/assets/js/components/Modal.js
+++ b/rdmo/core/assets/js/components/Modal.js
@@ -2,9 +2,9 @@ import React from 'react'
import PropTypes from 'prop-types'
import { Modal as BootstrapModal } from 'react-bootstrap'
-const Modal = ({ bsSize, buttonLabel, buttonProps, title, show, onClose, onSave, children }) => {
+const Modal = ({ title, show, modalProps, submitLabel, submitProps, onClose, onSubmit, children }) => {
return (
-
+
{title}
@@ -15,11 +15,12 @@ const Modal = ({ bsSize, buttonLabel, buttonProps, title, show, onClose, onSave,
- { onSave ?
-
- : null
+ {
+ onSubmit && (
+
+ )
}
@@ -27,14 +28,14 @@ const Modal = ({ bsSize, buttonLabel, buttonProps, title, show, onClose, onSave,
}
Modal.propTypes = {
- bsSize: PropTypes.oneOf(['lg', 'large', 'sm', 'small']),
- buttonLabel: PropTypes.string,
- buttonProps: PropTypes.object,
- children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
- onClose: PropTypes.func.isRequired,
- onSave: PropTypes.func,
- show: PropTypes.bool.isRequired,
title: PropTypes.string.isRequired,
+ show: PropTypes.bool.isRequired,
+ modalProps: PropTypes.object,
+ submitLabel: PropTypes.string,
+ submitProps: PropTypes.object,
+ onClose: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func,
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
}
export default Modal
diff --git a/rdmo/core/assets/js/containers/Pending.js b/rdmo/core/assets/js/containers/Pending.js
new file mode 100644
index 0000000000..07609f8a79
--- /dev/null
+++ b/rdmo/core/assets/js/containers/Pending.js
@@ -0,0 +1,24 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+import { isEmpty } from 'lodash'
+
+const Pending = ({ pending }) => {
+ return (
+ !isEmpty(pending.items) && (
+
+ )
+ )
+}
+
+Pending.propTypes = {
+ pending: PropTypes.object.isRequired,
+}
+
+function mapStateToProps(state) {
+ return {
+ pending: state.pending,
+ }
+}
+
+export default connect(mapStateToProps)(Pending)
diff --git a/rdmo/core/assets/js/reducers/configReducer.js b/rdmo/core/assets/js/reducers/configReducer.js
new file mode 100644
index 0000000000..9001d4e8c4
--- /dev/null
+++ b/rdmo/core/assets/js/reducers/configReducer.js
@@ -0,0 +1,22 @@
+import { updateConfig, deleteConfig, setConfigInLocalStorage, deleteConfigInLocalStorage } from '../utils/config'
+
+import { DELETE_CONFIG, UPDATE_CONFIG } from '../actions/actionTypes'
+
+const initialState = {}
+
+export default function configReducer(state = initialState, action) {
+ switch(action.type) {
+ case UPDATE_CONFIG:
+ if (action.ls) {
+ setConfigInLocalStorage(state.prefix, action.path, action.value)
+ }
+ return updateConfig(state, action.path, action.value)
+ case DELETE_CONFIG:
+ if (action.ls) {
+ deleteConfigInLocalStorage(state.prefix, action.path)
+ }
+ return deleteConfig(state, action.path)
+ default:
+ return state
+ }
+}
diff --git a/rdmo/core/assets/js/reducers/pendingReducer.js b/rdmo/core/assets/js/reducers/pendingReducer.js
new file mode 100644
index 0000000000..7f5b9de1cc
--- /dev/null
+++ b/rdmo/core/assets/js/reducers/pendingReducer.js
@@ -0,0 +1,16 @@
+import { ADD_TO_PENDING, REMOVE_FROM_PENDING } from '../actions/actionTypes'
+
+const initialState = {
+ items: []
+}
+
+export default function pendingReducer(state = initialState, action) {
+ switch(action.type) {
+ case ADD_TO_PENDING:
+ return { ...state, items: [...state.items, action.item] }
+ case REMOVE_FROM_PENDING:
+ return { ...state, items: state.items.filter((item) => (item != action.item)) }
+ default:
+ return state
+ }
+}
diff --git a/rdmo/core/assets/js/reducers/settingsReducer.js b/rdmo/core/assets/js/reducers/settingsReducer.js
new file mode 100644
index 0000000000..379f1829de
--- /dev/null
+++ b/rdmo/core/assets/js/reducers/settingsReducer.js
@@ -0,0 +1,14 @@
+import { FETCH_SETTINGS_ERROR, FETCH_SETTINGS_SUCCESS } from '../actions/actionTypes'
+
+const initialState = {}
+
+export default function settingsReducer(state = initialState, action) {
+ switch(action.type) {
+ case FETCH_SETTINGS_SUCCESS:
+ return { ...state, ...action.settings }
+ case FETCH_SETTINGS_ERROR:
+ return { ...state, errors: action.errors }
+ default:
+ return state
+ }
+}
diff --git a/rdmo/core/assets/js/reducers/templateReducer.js b/rdmo/core/assets/js/reducers/templateReducer.js
new file mode 100644
index 0000000000..b7897f2eb8
--- /dev/null
+++ b/rdmo/core/assets/js/reducers/templateReducer.js
@@ -0,0 +1,14 @@
+import { FETCH_TEMPLATES_ERROR, FETCH_TEMPLATES_SUCCESS } from '../actions/actionTypes'
+
+const initialState = {}
+
+export default function templateReducer(state = initialState, action) {
+ switch(action.type) {
+ case FETCH_TEMPLATES_SUCCESS:
+ return { ...state, ...action.templates }
+ case FETCH_TEMPLATES_ERROR:
+ return { ...state, errors: action.errors }
+ default:
+ return state
+ }
+}
diff --git a/rdmo/core/assets/js/reducers/userReducer.js b/rdmo/core/assets/js/reducers/userReducer.js
new file mode 100644
index 0000000000..8c76285f56
--- /dev/null
+++ b/rdmo/core/assets/js/reducers/userReducer.js
@@ -0,0 +1,18 @@
+import { FETCH_CURRENT_USER_ERROR, FETCH_CURRENT_USER_INIT, FETCH_CURRENT_USER_SUCCESS } from '../actions/actionTypes'
+
+const initialState = {
+ currentUser: {},
+}
+
+export default function userReducer(state = initialState, action) {
+ switch(action.type) {
+ case FETCH_CURRENT_USER_INIT:
+ return {...state, ...action.currentUser}
+ case FETCH_CURRENT_USER_SUCCESS:
+ return {...state, ...action.currentUser}
+ case FETCH_CURRENT_USER_ERROR:
+ return {...state, errors: action.error.errors}
+ default:
+ return state
+ }
+}
diff --git a/rdmo/core/assets/js/utils/baseUrl.js b/rdmo/core/assets/js/utils/baseUrl.js
deleted file mode 100644
index 22a17df960..0000000000
--- a/rdmo/core/assets/js/utils/baseUrl.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// take the baseurl from the of the django template
-export default document.querySelector('meta[name="baseurl"]').content.replace(/\/+$/, '')
diff --git a/rdmo/core/assets/js/utils/config.js b/rdmo/core/assets/js/utils/config.js
new file mode 100644
index 0000000000..22b1be6460
--- /dev/null
+++ b/rdmo/core/assets/js/utils/config.js
@@ -0,0 +1,58 @@
+import { set, unset, toNumber, isNaN } from 'lodash'
+
+const updateConfig = (config, path, value) => {
+ const newConfig = {...config}
+ set(newConfig, path, value)
+ return newConfig
+}
+
+const deleteConfig = (config, path) => {
+ const newConfig = {...config}
+ unset(newConfig, path)
+ return newConfig
+}
+
+const getConfigFromLocalStorage = (prefix) => {
+ const ls = {...localStorage}
+
+ return Object.entries(ls)
+ .filter(([lsPath,]) => lsPath.startsWith(prefix))
+ .map(([lsPath, lsValue]) => {
+ if (lsPath.startsWith(prefix)) {
+ const path = lsPath.replace(`${prefix}.`, '')
+
+ // check if it is literal 'true' or 'false'
+ if (lsValue === 'true') {
+ return [path, true]
+ } else if (lsValue === 'false') {
+ return [path, false]
+ }
+
+ // check if the value is number or a string
+ const numberValue = toNumber(lsValue)
+ if (isNaN(numberValue)) {
+ return [path, lsValue]
+ } else {
+ return [path, numberValue]
+ }
+ } else {
+ return null
+ }
+ })
+}
+
+const setConfigInLocalStorage = (prefix, path, value) => {
+ localStorage.setItem(`${prefix}.${path}`, value)
+}
+
+const deleteConfigInLocalStorage = (prefix, path) => {
+ localStorage.removeItem(`${prefix}.${path}`)
+}
+
+export {
+ updateConfig,
+ deleteConfig,
+ getConfigFromLocalStorage,
+ setConfigInLocalStorage,
+ deleteConfigInLocalStorage
+}
diff --git a/rdmo/core/assets/js/utils/index.js b/rdmo/core/assets/js/utils/index.js
index c2d32da182..30d03229a7 100644
--- a/rdmo/core/assets/js/utils/index.js
+++ b/rdmo/core/assets/js/utils/index.js
@@ -1,5 +1,2 @@
export * from './api'
-export { default as baseUrl } from './baseUrl'
-export { default as language } from './language'
-export { default as siteId } from './siteId'
-export { default as staticUrl } from './staticUrl'
+export { baseUrl, language, siteId, staticUrl } from './meta'
diff --git a/rdmo/core/assets/js/utils/language.js b/rdmo/core/assets/js/utils/language.js
deleted file mode 100644
index 58dc8a369e..0000000000
--- a/rdmo/core/assets/js/utils/language.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// take the language from the of the django template
-export default document.querySelector('meta[name="language"]').content
diff --git a/rdmo/core/assets/js/utils/meta.js b/rdmo/core/assets/js/utils/meta.js
new file mode 100644
index 0000000000..d8186e6c2f
--- /dev/null
+++ b/rdmo/core/assets/js/utils/meta.js
@@ -0,0 +1,9 @@
+// take information from the of the django template
+
+export const baseUrl = document.querySelector('meta[name="baseurl"]').content.replace(/\/+$/, '')
+
+export const staticUrl = document.querySelector('meta[name="staticurl"]').content.replace(/\/+$/, '')
+
+export const siteId = Number(document.querySelector('meta[name="site_id"]').content)
+
+export const language = document.querySelector('meta[name="language"]').content
diff --git a/rdmo/core/assets/js/utils/siteId.js b/rdmo/core/assets/js/utils/siteId.js
deleted file mode 100644
index 7b413b672e..0000000000
--- a/rdmo/core/assets/js/utils/siteId.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// take the site_id from the of the django template
-export default Number(document.querySelector('meta[name="site_id"]').content)
diff --git a/rdmo/core/assets/js/utils/staticUrl.js b/rdmo/core/assets/js/utils/staticUrl.js
deleted file mode 100644
index 0a1323cb10..0000000000
--- a/rdmo/core/assets/js/utils/staticUrl.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// take the staticurl from the of the django template
-export default document.querySelector('meta[name="staticurl"]').content.replace(/\/+$/, '')
diff --git a/rdmo/core/assets/js/utils/store.js b/rdmo/core/assets/js/utils/store.js
new file mode 100644
index 0000000000..d30203fd59
--- /dev/null
+++ b/rdmo/core/assets/js/utils/store.js
@@ -0,0 +1,14 @@
+import Cookies from 'js-cookie'
+import isEmpty from 'lodash/isEmpty'
+
+const checkStoreId = () => {
+ const currentStoreId = Cookies.get('storeid')
+ const localStoreId = localStorage.getItem('rdmo.storeid')
+
+ if (isEmpty(localStoreId) || localStoreId !== currentStoreId) {
+ localStorage.clear()
+ localStorage.setItem('rdmo.storeid', currentStoreId)
+ }
+}
+
+export { checkStoreId }
diff --git a/rdmo/core/assets/scss/base.scss b/rdmo/core/assets/scss/base.scss
new file mode 100644
index 0000000000..dadff686a5
--- /dev/null
+++ b/rdmo/core/assets/scss/base.scss
@@ -0,0 +1,15 @@
+$icon-font-path: "bootstrap-sass/assets/fonts/bootstrap/";
+@import '~bootstrap-sass';
+@import '~font-awesome/css/font-awesome.css';
+
+@import 'react-datepicker/dist/react-datepicker.css';
+
+@import 'variables';
+@import 'style';
+
+@import 'codemirror';
+@import 'fonts';
+@import 'footer';
+@import 'header';
+@import 'swagger';
+@import 'utils';
diff --git a/rdmo/core/assets/scss/codemirror.scss b/rdmo/core/assets/scss/codemirror.scss
new file mode 100644
index 0000000000..be9f2c0bef
--- /dev/null
+++ b/rdmo/core/assets/scss/codemirror.scss
@@ -0,0 +1,9 @@
+.CodeMirror {
+ font-family: DroidSans-Mono, mono;
+}
+
+formgroup .CodeMirror {
+ border-radius: 4px;
+ border: 1px solid #ccc;
+ color: #555;
+}
diff --git a/rdmo/core/assets/scss/fonts.scss b/rdmo/core/assets/scss/fonts.scss
new file mode 100644
index 0000000000..4d1f217320
--- /dev/null
+++ b/rdmo/core/assets/scss/fonts.scss
@@ -0,0 +1,44 @@
+@font-face {
+ font-family: "DroidSans";
+ src: url('../fonts/DroidSans.ttf');
+}
+@font-face {
+ font-family: "DroidSans";
+ src: url('../fonts/DroidSans-Bold.ttf');
+ font-weight: bold;
+}
+@font-face {
+ font-family: "DroidSans-Mono";
+ src: url('../fonts/DroidSansMono.ttf');
+}
+@font-face {
+ font-family: "DroidSerif";
+ src: url('../fonts/DroidSerif.ttf');
+}
+@font-face {
+ font-family: "DroidSerif";
+ src: url('../fonts/DroidSerif-Bold.ttf');
+ font-weight: bold;
+}
+@font-face {
+ font-family: "DroidSerif";
+ src: url('../fonts/DroidSerif-Italic.ttf');
+ font-style: italic;
+}
+@font-face {
+ font-family: "DroidSerif";
+ src: url('../fonts/DroidSerif-BoldItalic.ttf');
+ font-style: italic;
+ font-weight: bold;
+}
+
+body {
+ font-family: DroidSans, sans;
+}
+h1, h2, h3, h4, h5, h6 {
+ font-family: DroidSerif, serif;
+}
+
+a.fa {
+ text-decoration: none !important;
+}
diff --git a/rdmo/core/assets/scss/footer.scss b/rdmo/core/assets/scss/footer.scss
new file mode 100644
index 0000000000..7b4838c053
--- /dev/null
+++ b/rdmo/core/assets/scss/footer.scss
@@ -0,0 +1,55 @@
+$footer-height: 280px;
+$footer-height-md: 600px;
+$footer-height-sm: 260px;
+
+/* footer layout */
+
+.content {
+ min-height: 100%;
+ margin-bottom: -$footer-height;
+ padding-bottom: $footer-height;
+}
+footer {
+ height: $footer-height;
+}
+@media (max-width: $screen-sm-max) {
+ .content {
+ margin-bottom: -$footer-height-md;
+ padding-bottom: $footer-height-md;
+ }
+ footer {
+ height: $footer-height-md;
+ }
+}
+@media (max-width: $screen-xs-max) {
+ .content {
+ margin-bottom: -$footer-height-md;
+ padding-bottom: $footer-height-md;
+ }
+ footer {
+ height: $footer-height-md;
+ }
+}
+
+/* footer style */
+
+footer {
+ color: $footer-color;
+ background-color: $footer-background-color;
+ padding-top: 20px;
+
+ a,
+ a:visited,
+ a:hover {
+ color: $footer-link-color;
+ }
+ h4 {
+ color: $footer-link-color;
+ }
+ p {
+ text-align: left;
+ }
+ img {
+ display: block;
+ }
+}
diff --git a/rdmo/core/assets/scss/header.scss b/rdmo/core/assets/scss/header.scss
new file mode 100644
index 0000000000..769b65c0df
--- /dev/null
+++ b/rdmo/core/assets/scss/header.scss
@@ -0,0 +1,89 @@
+$header-height: 400px;
+$header-height-md: 300px;
+
+header {
+ position: relative;
+
+ height: $header-height;
+ background-color: black;
+
+ .header-image {
+ position: absolute;
+ left: 0;
+ right: 0;
+
+ opacity: 0;
+ -webkit-transition: $image-transition;
+ -moz-transition: $image-transition;
+ -ms-transition: $image-transition;
+ -o-transition: $image-transition;
+ transition: $image-transition;
+
+ &.visible {
+ opacity: 1;
+ }
+ img {
+ display: block;
+ width: 100%;
+ height: $header-height;
+ }
+ p {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ z-index: 10;
+
+ padding-right: 5px;
+ margin-bottom: 5px;
+ font-size: 10px;
+ color: $footer-link-color;
+
+ }
+ a,
+ a:visited,
+ a:hover {
+ color: $footer-link-color;
+ }
+ }
+ .header-text {
+ position: relative;
+ padding-top: 100px;
+
+ h1 {
+ font-size: 60px;
+ color: white;
+ }
+ p {
+ font-size: 30px;
+ color: white;
+ }
+ }
+}
+@media (max-width: $screen-md-max) {
+ header {
+ height: $header-height-md;
+ }
+ header .header-image img {
+ height: $header-height-md;
+ }
+ header .header-text {
+ padding-top: 50px;
+ }
+}
+@media (max-width: $screen-xs-max) {
+ header {
+ background-color: inherit;
+ height: auto;
+ }
+ header .header-text {
+ padding-top: 0;
+ }
+ header .header-text h1 {
+ font-size: 40px;
+ color: $headline-color;
+ }
+ header .header-text p {
+ font-size: 20px;
+ color: $variant-color;
+ }
+}
diff --git a/rdmo/core/assets/scss/style.scss b/rdmo/core/assets/scss/style.scss
new file mode 100644
index 0000000000..795a6f9228
--- /dev/null
+++ b/rdmo/core/assets/scss/style.scss
@@ -0,0 +1,497 @@
+html, body {
+ height: 100%;
+ background-color: $background-color;
+}
+
+h1, h2, h3, h4 {
+ color: $headline-color;
+ background-color: $headline-background-color;
+ line-height: 40px;
+}
+h5, h6 {
+ color: $headline-color;
+ background-color: $headline-background-color;
+ font-size: medium;
+ line-height: 20px;
+}
+h1 {
+ font-size: 28px;
+}
+h2 {
+ font-size: 24px;
+}
+.sidebar h2,
+.modal h2 {
+ font-size: 20px;
+}
+h3 {
+ font-size: 16px;
+}
+h4 {
+ font-size: 14px;
+}
+form {
+ margin-bottom: 20px;
+}
+.extend {
+ width: 100%;
+}
+
+a {
+ color: $link-color;
+
+ &:visited {
+ color: $link-color-visited;
+ }
+ &:hover {
+ color: $link-color-hover;
+ }
+ &:focus {
+ color: $link-color-focus;
+ }
+
+ &.btn {
+ color: white;
+
+ &:visited,
+ &:hover,
+ &:focus {
+ color: white;
+ }
+ }
+ &.text-warning {
+ &:visited,
+ &:hover,
+ &:focus {
+ color: #8a6d3b;
+ }
+ }
+ &.text-danger {
+ &:visited,
+ &:hover,
+ &:focus {
+ color: #a94442;
+ }
+ }
+
+ &.disabled {
+ cursor: not-allowed;
+ }
+}
+
+code {
+ word-wrap: break-word;
+
+ &.code-questions {
+ color: rgb(16, 31, 112);
+ background-color: rgba(16, 31, 112, 0.1);
+ }
+ &.code-options {
+ color: rgb(255, 100, 0);
+ background-color: rgba(255, 100, 0, 0.1);
+ }
+ &.code-options-provider {
+ color: white;
+ background-color: rgba(255, 100, 0, 0.8);
+ }
+ &.code-conditions {
+ color: rgb(128, 0, 128);
+ background-color: rgba(128, 0, 128, 0.1);
+ }
+ &.code-tasks {
+ color: rgb(128, 0, 0);
+ background-color: rgba(128, 0, 0, 0.1);
+ }
+ &.code-views {
+ color: rgb(0, 128, 0);
+ background-color: rgba(0, 128, 0, 0.1);
+ }
+ &.code-order {
+ color: rgb(96, 96, 96);
+ background-color: rgba(96, 96, 96, 0.1);
+ }
+ &.code-import {
+ color: black;
+ background-color: rgba(96, 96, 96, 0.1);
+ }
+}
+
+table {
+ p {
+ margin-bottom: 5px;
+ }
+ p:last-child {
+ margin-bottom: 0;
+ }
+}
+
+.table-break-word {
+ td {
+ word-break: break-all;
+ }
+}
+
+details {
+ margin-bottom: 10px;
+}
+
+summary {
+ display: list-item;
+ cursor: pointer;
+ margin-bottom: 5px;
+}
+
+metadata {
+ display: none;
+}
+
+/* navbar */
+
+.navbar-default {
+ background-color: $navigation-background-color;
+ border-bottom: none;
+
+ .navbar-brand,
+ .navbar-nav > li > a,
+ .navbar-nav > li > a:focus {
+ color: $navigation-color;
+ background-color: transparent;
+ }
+ .navbar-brand:hover,
+ .navbar-nav > li > a:hover,
+ .navbar-nav > .open > a,
+ .navbar-nav > .open > a:focus,
+ .navbar-nav > .open > a:hover {
+ color: $navigation-hover-color;
+ background-color: $navigation-hover-background-color;
+ }
+
+ .dropdown li.divider:first-child {
+ display: none;
+ }
+}
+
+/* content */
+
+.content {
+ padding-top: 50px; /* same height as the navbar */
+}
+.sidebar {
+ /* make the sidebar sticky */
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0;
+}
+.page, .sidebar {
+ height: 100%;
+ margin-top: 10px;
+ margin-bottom: 60px;
+}
+.page h2:nth-child(2) {
+ margin-top: 0;
+}
+.sidebar h2:first-child,
+.sidebar .import-buttons {
+ margin-top: 70px;
+}
+
+/* questions overview */
+
+.section-panel {
+
+}
+
+.subsection-panel {
+ margin-left: 40px;
+}
+
+.group-panel {
+ margin-left: 80px;
+
+ table th:first-child,
+ table td:first-child {
+ padding-left: 15px;
+ }
+
+ table th:last-child,
+ table td:last-child {
+ padding-right: 15px;
+ }
+}
+
+/* angular forms */
+
+.input-collection {
+ margin-bottom: 15px;
+}
+
+/* forms */
+
+.form-label {
+ margin-bottom: 5px;
+ font-weight: 700;
+}
+
+form .yesno label {
+ margin-right: 10px;
+}
+
+.row {
+ .checkbox,
+ .radio {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ }
+
+ @media (min-width: $screen-xs-max) {
+ .checkbox-padding .checkbox,
+ .radio-padding .radio {
+ margin-top: 32px;
+ margin-bottom: 11px;
+ }
+ }
+}
+
+.input-xs {
+ height: 24px;
+ padding: 5px 10px;
+ font-size: 11px;
+ line-height: 1;
+ border-radius: 2px;
+}
+
+.help-block.info {
+ margin-top: 0;
+}
+
+.sidebar-form {
+ display: flex;
+ gap: 5px;
+}
+
+.upload-form {
+ .upload-form-field {
+ position: relative;
+
+ cursor: pointer;
+ border-radius: 4px;
+
+ flex-grow: 1;
+ overflow: hidden;
+
+ p,
+ input {
+ height: 34px;
+ margin: 0px;
+ }
+
+ p {
+ text-align: left;
+ cursor: pointer;
+
+ color: $link-color;
+ border: 1px solid silver;
+ border-radius: 4px;
+
+ width: calc(100% - 1px);
+ padding: 6px 14px;
+
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ input {
+ position: absolute;
+ z-index: 1;
+ padding: 0;
+ opacity: 0;
+ }
+
+ &:hover {
+ background-color: #e6e6e6;
+ }
+ }
+}
+
+/* modals */
+
+.modal-body {
+ > p:last-child,
+ formgroup:last-child .form-group {
+ margin-bottom: 0;
+ }
+
+ .copy-block {
+ margin-bottom: 20px;
+ }
+
+ .help-block {
+ font-size: small;
+ word-break: break-word;
+ }
+
+ .nav.nav-tabs {
+ margin-bottom: 20px;
+ }
+}
+
+/* options */
+
+.options-dropdown {
+ display: inline-block;
+
+ > a {
+ cursor: pointer;
+ }
+}
+
+/* panels */
+
+.panel-default {
+ min-height: 5px;
+}
+
+.panel-body {
+ padding-top: 10px;
+ padding-bottom: 10px;
+}
+
+.panel li > p:last-child {
+ margin-bottom: 0;
+}
+
+/* lists */
+
+ul.list-arrow li {
+ margin-left: 20px;
+
+ &.active {
+ margin-left: 0;
+ }
+
+ &.active a:before {
+ float: left;
+ width: 20px;
+ text-align: right;
+ content: '\2192\0000a0'; /* right-arrow followed by a space */
+ }
+}
+
+/* misc */
+.form-errors {
+ margin-bottom: 20px;
+}
+li > a.control-label > i {
+ display: none;
+}
+li.has-error > a.control-label > i,
+li.has-warning > a.control-label > i {
+ display: inline;
+}
+.email-form label,
+.connections-form label {
+ display: block;
+ margin: 0;
+ line-height: 40px;
+ border-bottom: 1px solid $modal-border-color;
+}
+.email-form label:first-child,
+.connections-form label:first-child {
+ border-top: 1px solid $modal-border-color;
+}
+.email-form label input,
+.connections-form label input {
+ margin-left: 5px;
+ margin-right: 5px;
+}
+.email-form .email-form-buttons,
+.connections-form .connections-form-buttons {
+ margin-top: 10px;
+}
+.socialaccount_providers {
+ margin: 0;
+ padding: 0;
+ height: 42px;
+}
+.socialaccount_providers li {
+ float: left;
+ margin: 0 5px 10px 5px;
+ list-style: none;
+}
+.socialaccount_providers li.socialaccount_provider_break {
+ float: none;
+ margin-left: 0;
+ margin-right: 0;
+}
+.socialaccount_provider_name {
+ line-height: 29px;
+ font-weight: bold;
+}
+.logout-form {
+ margin: 0;
+}
+.logout-form .btn-link {
+ padding: 3px 20px;
+ color: $navigation-dropdown-color;
+ display: block;
+ width: 100%;
+ text-align: left;
+ border: none;
+ clear: both;
+ font-weight: 400;
+ line-height: 1.42857143;
+ white-space: nowrap;
+}
+.logout-form .btn-link:hover {
+ color: $navigation-dropdown-hover-color;
+ background-color: $navigation-dropdown-hover-background-color;
+ text-decoration: none;
+}
+.logout-form .btn-link:focus {
+ color: $navigation-dropdown-hover-color;
+ background-color: $navigation-dropdown-hover-background-color;
+ text-decoration: none;
+ outline: none;
+}
+.rdmo-logo {
+ width: 240px;
+ margin-top: 40px;
+}
+
+// adjust background "hover" color in select2 to $link-color
+.select2-results__option--highlighted{
+ background-color: $link-color !important,
+}
+
+.cc-myself {
+ .checkbox {
+ margin: 0;
+ }
+}
+
+.ng-binding {
+ :last-child {
+ margin-bottom: 0;
+ }
+}
+
+.inline_image {
+ max-width: 100%;
+}
+
+[data-toggle="tooltip"] {
+ cursor: help;
+ text-decoration: underline;
+ text-decoration-style: dotted;
+}
+
+.more,
+.show-less {
+ display: none;
+}
+.show-more,
+.show-less {
+ color: $link-color;
+ cursor: pointer;
+}
diff --git a/rdmo/core/assets/scss/swagger.scss b/rdmo/core/assets/scss/swagger.scss
new file mode 100644
index 0000000000..0a0ecc6cac
--- /dev/null
+++ b/rdmo/core/assets/scss/swagger.scss
@@ -0,0 +1,28 @@
+.topbar {
+ background-color: $headline-color !important;
+}
+
+.swagger-ui .info {
+ margin: 30px;
+}
+
+.swagger-ui .btn.authorize {
+ border-color: $footer-background-color;
+ color: $text-color;
+}
+
+.swagger-ui .btn.authorize svg {
+ fill: $footer-background-color;
+}
+
+.swagger-ui .btn.authorize {
+ color: $footer-background-color !important;
+}
+
+.topbar img {
+ filter: hue-rotate(180deg)
+}
+
+.download-url-wrapper .download-url-button {
+ background-color: $headline-color !important;
+}
diff --git a/rdmo/core/assets/scss/utils.scss b/rdmo/core/assets/scss/utils.scss
new file mode 100644
index 0000000000..0e45f92470
--- /dev/null
+++ b/rdmo/core/assets/scss/utils.scss
@@ -0,0 +1,92 @@
+.flip {
+ transform: rotate(180deg) scaleX(-1);
+}
+
+.w-100 {
+ width: 100%;
+}
+.mt-0 {
+ margin-top: 0;
+}
+.mt-5 {
+ margin-top: 5px;
+}
+.mt-10 {
+ margin-top: 10px;
+}
+.mt-20 {
+ margin-top: 20px;
+}
+.mr-0 {
+ margin-right: 0;
+}
+.mr-5 {
+ margin-right: 5px;
+}
+.mr-10 {
+ margin-right: 10px;
+}
+.mr-20 {
+ margin-right: 20px;
+}
+.mb-0 {
+ margin-bottom: 0;
+}
+.mb-5 {
+ margin-bottom: 5px;
+}
+.mb-10 {
+ margin-bottom: 10px;
+}
+.mb-20 {
+ margin-bottom: 20px;
+}
+.ml-0 {
+ margin-left: 0;
+}
+.ml-5 {
+ margin-left: 5px;
+}
+.ml-10 {
+ margin-left: 10px;
+}
+.ml-20 {
+ margin-left: 20px;
+}
+
+.pt-0 {
+ padding-top: 0;
+}
+.pt-10 {
+ padding-top: 10px;
+}
+.pt-20 {
+ padding-top: 20px;
+}
+.pr-0 {
+ padding-right: 0;
+}
+.pr-10 {
+ padding-right: 10px;
+}
+.pr-20 {
+ padding-right: 20px;
+}
+.pb-0 {
+ padding-bottom: 0;
+}
+.pb-10 {
+ padding-bottom: 10px;
+}
+.pb-20 {
+ padding-bottom: 20px;
+}
+.pl-0 {
+ padding-left: 0;
+}
+.pl-10 {
+ padding-left: 10px;
+}
+.pl-20 {
+ padding-left: 20px;
+}
diff --git a/rdmo/core/settings.py b/rdmo/core/settings.py
index 422e7886fc..999d5be52b 100644
--- a/rdmo/core/settings.py
+++ b/rdmo/core/settings.py
@@ -218,6 +218,8 @@
'PROJECT_TABLE_PAGE_SIZE'
]
+TEMPLATES_API = []
+
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
DEFAULT_FROM_EMAIL = 'info@example.com'
diff --git a/rdmo/core/tests/test_viewset_templates.py b/rdmo/core/tests/test_viewset_templates.py
new file mode 100644
index 0000000000..ea90ffc989
--- /dev/null
+++ b/rdmo/core/tests/test_viewset_templates.py
@@ -0,0 +1,32 @@
+import pytest
+
+from django.urls import reverse
+
+users = (
+ ('owner', 'owner'),
+ ('manager', 'manager'),
+ ('author', 'author'),
+ ('guest', 'guest'),
+ ('api', 'api'),
+ ('user', 'user'),
+ ('anonymous', None),
+)
+
+status_map = {
+ 'list': {
+ 'owner': 200, 'manager': 200, 'author': 200, 'guest': 200, 'api': 200, 'user': 200, 'anonymous': 401
+ }
+}
+
+urlnames = {
+ 'list': 'template-list',
+}
+
+
+@pytest.mark.parametrize('username,password', users)
+def test_list(db, client, username, password):
+ client.login(username=username, password=password)
+
+ url = reverse(urlnames['list'])
+ response = client.get(url)
+ assert response.status_code == status_map['list'][username], response.json()
diff --git a/rdmo/core/urls/v1.py b/rdmo/core/urls/v1.py
index 442d612de5..6e7139a9a4 100644
--- a/rdmo/core/urls/v1.py
+++ b/rdmo/core/urls/v1.py
@@ -2,12 +2,13 @@
from rest_framework import routers
-from ..viewsets import GroupViewSet, SettingsViewSet, SitesViewSet
+from ..viewsets import GroupViewSet, SettingsViewSet, SitesViewSet, TemplatesViewSet
router = routers.DefaultRouter()
router.register(r'settings', SettingsViewSet, basename='setting')
router.register(r'sites', SitesViewSet, basename='site')
router.register(r'groups', GroupViewSet, basename='group')
+router.register(r'templates', TemplatesViewSet, basename='template')
urlpatterns = [
path('accounts/', include('rdmo.accounts.urls.v1')),
diff --git a/rdmo/core/viewsets.py b/rdmo/core/viewsets.py
index eaa97a2072..ea4e41af1a 100644
--- a/rdmo/core/viewsets.py
+++ b/rdmo/core/viewsets.py
@@ -1,6 +1,9 @@
+from pathlib import Path
+
from django.conf import settings
from django.contrib.auth.models import Group
from django.contrib.sites.models import Site
+from django.template.loader import get_template
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
@@ -31,3 +34,14 @@ class GroupViewSet(viewsets.ReadOnlyModelViewSet):
permission_classes = (HasModelPermission, )
queryset = Group.objects.all()
serializer_class = GroupSerializer
+
+
+class TemplatesViewSet(viewsets.GenericViewSet):
+
+ permission_classes = (IsAuthenticated, )
+
+ def list(self, request, *args, **kwargs):
+ return Response({
+ Path(template_path).stem: get_template(template_path).render(request=request).strip()
+ for template_path in settings.TEMPLATES_API
+ })
diff --git a/rdmo/management/assets/js/reducers/configReducer.js b/rdmo/management/assets/js/reducers/configReducer.js
index 31029c17f0..1476203c16 100644
--- a/rdmo/management/assets/js/reducers/configReducer.js
+++ b/rdmo/management/assets/js/reducers/configReducer.js
@@ -1,6 +1,6 @@
import set from 'lodash/set'
-import baseUrl from 'rdmo/core/assets/js/utils/baseUrl'
+import { baseUrl } from 'rdmo/core/assets/js/utils/meta'
const initialState = {
baseUrl: baseUrl + '/management/',
diff --git a/rdmo/projects/assets/js/projects.js b/rdmo/projects/assets/js/projects.js
index 414a8c915f..e4c4e562e7 100644
--- a/rdmo/projects/assets/js/projects.js
+++ b/rdmo/projects/assets/js/projects.js
@@ -2,12 +2,12 @@ import React from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
-import configureStore from './store/configureStore'
+import configureStore from './projects/store/configureStore'
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
-import Main from './containers/Main'
+import Main from './projects/containers/Main'
const store = configureStore()
diff --git a/rdmo/projects/assets/js/actions/actionTypes.js b/rdmo/projects/assets/js/projects/actions/actionTypes.js
similarity index 100%
rename from rdmo/projects/assets/js/actions/actionTypes.js
rename to rdmo/projects/assets/js/projects/actions/actionTypes.js
diff --git a/rdmo/projects/assets/js/actions/configActions.js b/rdmo/projects/assets/js/projects/actions/configActions.js
similarity index 100%
rename from rdmo/projects/assets/js/actions/configActions.js
rename to rdmo/projects/assets/js/projects/actions/configActions.js
diff --git a/rdmo/projects/assets/js/actions/projectsActions.js b/rdmo/projects/assets/js/projects/actions/projectsActions.js
similarity index 100%
rename from rdmo/projects/assets/js/actions/projectsActions.js
rename to rdmo/projects/assets/js/projects/actions/projectsActions.js
diff --git a/rdmo/projects/assets/js/actions/userActions.js b/rdmo/projects/assets/js/projects/actions/userActions.js
similarity index 100%
rename from rdmo/projects/assets/js/actions/userActions.js
rename to rdmo/projects/assets/js/projects/actions/userActions.js
diff --git a/rdmo/projects/assets/js/projects/api/AccountsApi.js b/rdmo/projects/assets/js/projects/api/AccountsApi.js
new file mode 100644
index 0000000000..94e42a94ad
--- /dev/null
+++ b/rdmo/projects/assets/js/projects/api/AccountsApi.js
@@ -0,0 +1,9 @@
+import BaseApi from 'rdmo/core/assets/js/api/BaseApi'
+
+class AccountsApi extends BaseApi {
+ static fetchCurrentUser() {
+ return this.get('/api/v1/accounts/users/current/')
+ }
+}
+
+export default AccountsApi
diff --git a/rdmo/projects/assets/js/api/ProjectsApi.js b/rdmo/projects/assets/js/projects/api/ProjectsApi.js
similarity index 98%
rename from rdmo/projects/assets/js/api/ProjectsApi.js
rename to rdmo/projects/assets/js/projects/api/ProjectsApi.js
index 5fc35590ad..d07cb6a54d 100644
--- a/rdmo/projects/assets/js/api/ProjectsApi.js
+++ b/rdmo/projects/assets/js/projects/api/ProjectsApi.js
@@ -1,7 +1,7 @@
import Cookies from 'js-cookie'
import BaseApi from 'rdmo/core/assets/js/api/BaseApi'
import { encodeParams } from 'rdmo/core/assets/js/utils/api'
-import baseUrl from 'rdmo/core/assets/js/utils/baseUrl'
+import { baseUrl } from 'rdmo/core/assets/js/utils/meta'
function BadRequestError(errors) {
this.errors = errors
diff --git a/rdmo/projects/assets/js/components/helper/PendingInvitations.js b/rdmo/projects/assets/js/projects/components/helper/PendingInvitations.js
similarity index 100%
rename from rdmo/projects/assets/js/components/helper/PendingInvitations.js
rename to rdmo/projects/assets/js/projects/components/helper/PendingInvitations.js
diff --git a/rdmo/projects/assets/js/components/helper/ProjectFilters.js b/rdmo/projects/assets/js/projects/components/helper/ProjectFilters.js
similarity index 100%
rename from rdmo/projects/assets/js/components/helper/ProjectFilters.js
rename to rdmo/projects/assets/js/projects/components/helper/ProjectFilters.js
diff --git a/rdmo/projects/assets/js/components/helper/ProjectImport.js b/rdmo/projects/assets/js/projects/components/helper/ProjectImport.js
similarity index 100%
rename from rdmo/projects/assets/js/components/helper/ProjectImport.js
rename to rdmo/projects/assets/js/projects/components/helper/ProjectImport.js
diff --git a/rdmo/projects/assets/js/components/helper/Table.js b/rdmo/projects/assets/js/projects/components/helper/Table.js
similarity index 100%
rename from rdmo/projects/assets/js/components/helper/Table.js
rename to rdmo/projects/assets/js/projects/components/helper/Table.js
diff --git a/rdmo/projects/assets/js/components/helper/index.js b/rdmo/projects/assets/js/projects/components/helper/index.js
similarity index 100%
rename from rdmo/projects/assets/js/components/helper/index.js
rename to rdmo/projects/assets/js/projects/components/helper/index.js
diff --git a/rdmo/projects/assets/js/components/main/Projects.js b/rdmo/projects/assets/js/projects/components/main/Projects.js
similarity index 100%
rename from rdmo/projects/assets/js/components/main/Projects.js
rename to rdmo/projects/assets/js/projects/components/main/Projects.js
diff --git a/rdmo/projects/assets/js/containers/Main.js b/rdmo/projects/assets/js/projects/containers/Main.js
similarity index 100%
rename from rdmo/projects/assets/js/containers/Main.js
rename to rdmo/projects/assets/js/projects/containers/Main.js
diff --git a/rdmo/projects/assets/js/hooks/useDatePicker.js b/rdmo/projects/assets/js/projects/hooks/useDatePicker.js
similarity index 100%
rename from rdmo/projects/assets/js/hooks/useDatePicker.js
rename to rdmo/projects/assets/js/projects/hooks/useDatePicker.js
diff --git a/rdmo/projects/assets/js/reducers/configReducer.js b/rdmo/projects/assets/js/projects/reducers/configReducer.js
similarity index 100%
rename from rdmo/projects/assets/js/reducers/configReducer.js
rename to rdmo/projects/assets/js/projects/reducers/configReducer.js
diff --git a/rdmo/projects/assets/js/reducers/projectsReducer.js b/rdmo/projects/assets/js/projects/reducers/projectsReducer.js
similarity index 100%
rename from rdmo/projects/assets/js/reducers/projectsReducer.js
rename to rdmo/projects/assets/js/projects/reducers/projectsReducer.js
diff --git a/rdmo/projects/assets/js/reducers/rootReducer.js b/rdmo/projects/assets/js/projects/reducers/rootReducer.js
similarity index 100%
rename from rdmo/projects/assets/js/reducers/rootReducer.js
rename to rdmo/projects/assets/js/projects/reducers/rootReducer.js
diff --git a/rdmo/projects/assets/js/reducers/userReducer.js b/rdmo/projects/assets/js/projects/reducers/userReducer.js
similarity index 100%
rename from rdmo/projects/assets/js/reducers/userReducer.js
rename to rdmo/projects/assets/js/projects/reducers/userReducer.js
diff --git a/rdmo/projects/assets/js/store/configureStore.js b/rdmo/projects/assets/js/projects/store/configureStore.js
similarity index 100%
rename from rdmo/projects/assets/js/store/configureStore.js
rename to rdmo/projects/assets/js/projects/store/configureStore.js
diff --git a/rdmo/projects/assets/js/utils/constants.js b/rdmo/projects/assets/js/projects/utils/constants.js
similarity index 100%
rename from rdmo/projects/assets/js/utils/constants.js
rename to rdmo/projects/assets/js/projects/utils/constants.js
diff --git a/rdmo/projects/assets/js/utils/getProjectTitlePath.js b/rdmo/projects/assets/js/projects/utils/getProjectTitlePath.js
similarity index 100%
rename from rdmo/projects/assets/js/utils/getProjectTitlePath.js
rename to rdmo/projects/assets/js/projects/utils/getProjectTitlePath.js
diff --git a/rdmo/projects/assets/js/utils/getUserRoles.js b/rdmo/projects/assets/js/projects/utils/getUserRoles.js
similarity index 100%
rename from rdmo/projects/assets/js/utils/getUserRoles.js
rename to rdmo/projects/assets/js/projects/utils/getUserRoles.js
diff --git a/rdmo/projects/assets/js/utils/index.js b/rdmo/projects/assets/js/projects/utils/index.js
similarity index 100%
rename from rdmo/projects/assets/js/utils/index.js
rename to rdmo/projects/assets/js/projects/utils/index.js
diff --git a/rdmo/projects/assets/js/utils/translations.js b/rdmo/projects/assets/js/projects/utils/translations.js
similarity index 100%
rename from rdmo/projects/assets/js/utils/translations.js
rename to rdmo/projects/assets/js/projects/utils/translations.js
diff --git a/rdmo/projects/assets/js/utils/userIsManager.js b/rdmo/projects/assets/js/projects/utils/userIsManager.js
similarity index 82%
rename from rdmo/projects/assets/js/utils/userIsManager.js
rename to rdmo/projects/assets/js/projects/utils/userIsManager.js
index 435d866a33..9b4e77430d 100644
--- a/rdmo/projects/assets/js/utils/userIsManager.js
+++ b/rdmo/projects/assets/js/projects/utils/userIsManager.js
@@ -1,4 +1,4 @@
-import siteId from 'rdmo/core/assets/js/utils/siteId'
+import { siteId } from 'rdmo/core/assets/js/utils/meta'
const userIsManager = (currentUser) => {
if (currentUser.is_superuser ||
diff --git a/webpack.config.js b/webpack.config.js
index cc71e803bb..d1d630dd32 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -6,6 +6,18 @@ const TerserPlugin = require('terser-webpack-plugin')
// list of separate config objects for each django app and their corresponding java script applications
const configList = [
+ {
+ name: 'core',
+ entry: {
+ base: [
+ './rdmo/core/assets/js/base.js',
+ './rdmo/core/assets/scss/base.scss'
+ ]
+ },
+ output: {
+ path: path.resolve(__dirname, './rdmo/core/static/core/'),
+ }
+ },
{
name: 'management',
entry: {
@@ -15,7 +27,6 @@ const configList = [
]
},
output: {
- filename: 'js/management.js',
path: path.resolve(__dirname, './rdmo/management/static/management/'),
}
},
@@ -28,7 +39,6 @@ const configList = [
]
},
output: {
- filename: 'js/projects.js',
path: path.resolve(__dirname, './rdmo/projects/static/projects/'),
}
}
@@ -42,6 +52,9 @@ const baseConfig = {
},
extensions: ['*', '.js', '.jsx']
},
+ output: {
+ filename: 'js/[name].js'
+ },
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].css',