Auto generate admin fields

This commit is contained in:
Pierre Burgy 2017-07-06 17:51:13 +02:00
parent feaaf3a4eb
commit 1fb39080c3
6 changed files with 115 additions and 27 deletions

View File

@ -40,7 +40,7 @@ class EditFormRelation extends React.Component { // eslint-disable-line react/pr
} }
// Request URL // Request URL
const requestUrlSuffix = query ? '' : this.props.record.get(this.props.relation.attribute); const requestUrlSuffix = query && this.props.record.get(this.props.relation.attribute) ? this.props.record.get(this.props.relation.attribute) : '';
const requestUrl = `/content-manager/explorer/${this.props.relation.model}/${requestUrlSuffix}`; const requestUrl = `/content-manager/explorer/${this.props.relation.model}/${requestUrlSuffix}`;
// Call our request helper (see 'utils/request') // Call our request helper (see 'utils/request')
@ -51,11 +51,11 @@ class EditFormRelation extends React.Component { // eslint-disable-line react/pr
.then(response => { .then(response => {
const options = _.isArray(response) const options = _.isArray(response)
? _.map(response, item => ({ ? _.map(response, item => ({
value: Number(item.id), value: item.id,
label: item[this.props.relation.displayedAttribute], label: item[this.props.relation.displayedAttribute],
})) }))
: [{ : [{
value: Number(response.id), value: response.id,
label: response[this.props.relation.displayedAttribute], label: response[this.props.relation.displayedAttribute],
}]; }];

View File

@ -8,21 +8,33 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect'; import { createStructuredSelector } from 'reselect';
import _ from 'lodash';
import config from '../../../../config/admin.json';
import { loadModels, updateSchema } from './actions'; import { loadModels, updateSchema } from './actions';
import { makeSelectModels, makeSelectLoading } from './selectors'; import { makeSelectLoading } from './selectors';
const tryRequire = (path) => {
try {
return require(`containers/${path}.js`); // eslint-disable-line global-require
} catch (err) {
return null;
}
};
class App extends React.Component { class App extends React.Component {
componentWillMount() { componentWillMount() {
this.props.loadModels(); const config = tryRequire('../../../../config/admin.json');
if (!_.isEmpty(_.get(config, 'admin.schema'))) {
this.props.updateSchema(config.admin.schema); this.props.updateSchema(config.admin.schema);
} else {
this.props.loadModels();
}
} }
render() { render() {
let content = <div />; let content = <div />;
if (this.props.models) { if (!this.props.loading) {
// Assign plugin component to children // Assign plugin component to children
content = React.Children.map(this.props.children, child => content = React.Children.map(this.props.children, child =>
React.cloneElement(child, { React.cloneElement(child, {
@ -46,11 +58,8 @@ App.contextTypes = {
App.propTypes = { App.propTypes = {
children: React.PropTypes.node.isRequired, children: React.PropTypes.node.isRequired,
exposedComponents: React.PropTypes.object.isRequired, exposedComponents: React.PropTypes.object.isRequired,
loading: React.PropTypes.bool,
loadModels: React.PropTypes.func.isRequired, loadModels: React.PropTypes.func.isRequired,
models: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.bool,
]),
updateSchema: React.PropTypes.func.isRequired, updateSchema: React.PropTypes.func.isRequired,
}; };
@ -63,7 +72,6 @@ export function mapDispatchToProps(dispatch) {
} }
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
models: makeSelectModels(),
loading: makeSelectLoading(), loading: makeSelectLoading(),
}); });

View File

@ -9,7 +9,7 @@ import { fromJS } from 'immutable';
import { LOAD_MODELS, LOADED_MODELS, UPDATE_SCHEMA } from './constants'; import { LOAD_MODELS, LOADED_MODELS, UPDATE_SCHEMA } from './constants';
const initialState = fromJS({ const initialState = fromJS({
loading: false, loading: true,
models: false, models: false,
schema: false, schema: false,
}); });
@ -17,11 +17,14 @@ const initialState = fromJS({
function appReducer(state = initialState, action) { function appReducer(state = initialState, action) {
switch (action.type) { switch (action.type) {
case LOAD_MODELS: case LOAD_MODELS:
return state.set('loading', true); return state;
case LOADED_MODELS: case LOADED_MODELS:
return state.set('loading', false).set('models', action.models); return state
.set('models', action.models);
case UPDATE_SCHEMA: case UPDATE_SCHEMA:
return state.set('schema', action.schema); return state
.set('loading', false)
.set('schema', action.schema);
default: default:
return state; return state;
} }

View File

@ -1,9 +1,13 @@
import _ from 'lodash'; import _ from 'lodash';
import {takeLatest} from 'redux-saga'; import { takeLatest } from 'redux-saga';
import {fork, put} from 'redux-saga/effects'; import { fork, put, select } from 'redux-saga/effects';
import { generateSchema } from 'utils/schema';
import { loadedModels, updateSchema } from './actions';
import { LOAD_MODELS, LOADED_MODELS, UPDATE_SCHEMA } from './constants';
import { makeSelectModels } from './selectors';
import {loadedModels} from './actions';
import {LOAD_MODELS, UPDATE_SCHEMA} from './constants';
export function* getModels() { export function* getModels() {
try { try {
@ -26,6 +30,22 @@ export function* getModels() {
} }
} }
export function* modelsLoaded() {
const models = yield select(makeSelectModels());
let schema;
try {
schema = generateSchema(models);
} catch (err) {
window.Strapi.notification.error(
'An error occurred during schema generation.'
);
throw new Error(err);
}
yield put(updateSchema(schema));
}
export function* schemaUpdated(action) { export function* schemaUpdated(action) {
// Display the links only if the `displayed` attribute is not set to false // Display the links only if the `displayed` attribute is not set to false
const displayedModels = _.pickBy(action.schema, model => (model.displayed !== false)); const displayedModels = _.pickBy(action.schema, model => (model.displayed !== false));
@ -44,6 +64,7 @@ export function* schemaUpdated(action) {
export function* defaultSaga() { export function* defaultSaga() {
yield fork(takeLatest, LOAD_MODELS, getModels); yield fork(takeLatest, LOAD_MODELS, getModels);
yield fork(takeLatest, UPDATE_SCHEMA, schemaUpdated); yield fork(takeLatest, UPDATE_SCHEMA, schemaUpdated);
yield fork(takeLatest, LOADED_MODELS, modelsLoaded);
} }
// All sagas to be loaded // All sagas to be loaded

View File

@ -0,0 +1,56 @@
import _ from 'lodash';
import pluralize from 'pluralize';
/**
* Generate a schema according to the models
* of the Strapi application.
*
* @param models
*/
const generateSchema = (models) => {
// Init `schema` object
const schema = {};
_.forEach(models, (model, name) => {
// Model data
const schemaModel = {
label: _.upperFirst(name),
labelPlural: _.upperFirst(pluralize(name)),
orm: model.orm || 'mongoose',
};
// Fields (non relation)
schemaModel.fields = _.mapValues(_.pickBy(model.attributes, attribute =>
!attribute.model && !attribute.collection
), (value, attribute) => ({
label: _.upperFirst(attribute),
description: '',
type: value.type || 'string',
}));
// Select fields displayed in list view
schemaModel.list = _.slice(_.keys(schemaModel.fields), 0, 4);
// Model relations
schemaModel.relations = _.mapValues(_.pickBy(model.attributes, attribute =>
attribute.model
), (value, attribute) => ({
columnName: attribute,
model: value.model,
attribute,
label: _.upperFirst(attribute),
description: '',
displayedAttribute: _.findKey(models[value.model].attributes, { type: 'string' }) || 'id',
})
);
// Set the formatted model to the schema
schema[name] = schemaModel;
});
return schema;
};
export {
generateSchema,
};

View File

@ -23,7 +23,7 @@ module.exports = {
find: async ctx => { find: async ctx => {
const model = strapi.models[ctx.params.model]; const model = strapi.models[ctx.params.model];
const orm = _.get(strapi.plugins, ['content-manager', 'config', 'admin', 'schema', ctx.params.model, 'orm']); const orm = _.get(strapi.plugins, ['content-manager', 'config', 'admin', 'schema', ctx.params.model, 'orm']) || model.orm;
const queries = _.get(strapi.plugins, ['content-manager', 'config', 'queries', orm]); const queries = _.get(strapi.plugins, ['content-manager', 'config', 'queries', orm]);
const primaryKey = model.primaryKey; const primaryKey = model.primaryKey;
const {limit, skip = 0, sort = primaryKey, query, queryAttribute} = ctx.request.query; const {limit, skip = 0, sort = primaryKey, query, queryAttribute} = ctx.request.query;
@ -44,7 +44,7 @@ module.exports = {
count: async ctx => { count: async ctx => {
const model = strapi.models[ctx.params.model]; const model = strapi.models[ctx.params.model];
const orm = _.get(strapi.plugins, ['content-manager', 'config', 'admin', 'schema', ctx.params.model, 'orm']); const orm = _.get(strapi.plugins, ['content-manager', 'config', 'admin', 'schema', ctx.params.model, 'orm']) || model.orm;
const queries = _.get(strapi.plugins, ['content-manager', 'config', 'queries', orm]); const queries = _.get(strapi.plugins, ['content-manager', 'config', 'queries', orm]);
// Count using `queries` system // Count using `queries` system
@ -57,7 +57,7 @@ module.exports = {
findOne: async ctx => { findOne: async ctx => {
const model = strapi.models[ctx.params.model]; const model = strapi.models[ctx.params.model];
const orm = _.get(strapi.plugins, ['content-manager', 'config', 'admin', 'schema', ctx.params.model, 'orm']); const orm = _.get(strapi.plugins, ['content-manager', 'config', 'admin', 'schema', ctx.params.model, 'orm']) || model.orm;
const queries = _.get(strapi.plugins, ['content-manager', 'config', 'queries', orm]); const queries = _.get(strapi.plugins, ['content-manager', 'config', 'queries', orm]);
const primaryKey = model.primaryKey; const primaryKey = model.primaryKey;
const id = ctx.params.id; const id = ctx.params.id;
@ -79,7 +79,7 @@ module.exports = {
create: async ctx => { create: async ctx => {
const model = strapi.models[ctx.params.model]; const model = strapi.models[ctx.params.model];
const orm = _.get(strapi.plugins, ['content-manager', 'config', 'admin', 'schema', ctx.params.model, 'orm']); const orm = _.get(strapi.plugins, ['content-manager', 'config', 'admin', 'schema', ctx.params.model, 'orm']) || model.orm;
const queries = _.get(strapi.plugins, ['content-manager', 'config', 'queries', orm]); const queries = _.get(strapi.plugins, ['content-manager', 'config', 'queries', orm]);
const values = ctx.request.body; const values = ctx.request.body;
@ -94,7 +94,7 @@ module.exports = {
update: async ctx => { update: async ctx => {
const model = strapi.models[ctx.params.model]; const model = strapi.models[ctx.params.model];
const orm = _.get(strapi.plugins, ['content-manager', 'config', 'admin', 'schema', ctx.params.model, 'orm']); const orm = _.get(strapi.plugins, ['content-manager', 'config', 'admin', 'schema', ctx.params.model, 'orm']) || model.orm;
const queries = _.get(strapi.plugins, ['content-manager', 'config', 'queries', orm]); const queries = _.get(strapi.plugins, ['content-manager', 'config', 'queries', orm]);
const primaryKey = model.primaryKey; const primaryKey = model.primaryKey;
const id = ctx.params.id; const id = ctx.params.id;
@ -113,7 +113,7 @@ module.exports = {
delete: async ctx => { delete: async ctx => {
const model = strapi.models[ctx.params.model]; const model = strapi.models[ctx.params.model];
const orm = _.get(strapi.plugins, ['content-manager', 'config', 'admin', 'schema', ctx.params.model, 'orm']); const orm = _.get(strapi.plugins, ['content-manager', 'config', 'admin', 'schema', ctx.params.model, 'orm']) || model.orm;
const queries = _.get(strapi.plugins, ['content-manager', 'config', 'queries', orm]); const queries = _.get(strapi.plugins, ['content-manager', 'config', 'queries', orm]);
const primaryKey = model.primaryKey; const primaryKey = model.primaryKey;
const id = ctx.params.id; const id = ctx.params.id;