From 59b5229536fa2bf896010a7e543bee9d2d38459a Mon Sep 17 00:00:00 2001 From: Peter Siemens Date: Fri, 26 Oct 2018 00:38:18 -0700 Subject: [PATCH 1/2] Automatically connect field schemas --- .../migrations/0022_auto_20181024_0933.py | 20 +++ .../manager/src/js/actions/ZonesActions.js | 162 +++++++++--------- .../src/js/components/ZoneEditor/index.js | 53 +++--- .../src/js/components/fields/ArticleField.js | 9 +- .../src/js/components/fields/EventField.js | 9 +- .../src/js/components/fields/ImageField.js | 9 +- .../src/js/components/fields/PodcastField.js | 9 +- .../src/js/components/fields/PollField.js | 9 +- .../src/js/components/fields/TopicField.js | 9 +- .../src/js/components/fields/WidgetField.js | 12 +- .../manager/src/js/components/fields/index.js | 36 ++-- 11 files changed, 208 insertions(+), 129 deletions(-) create mode 100644 dispatch/migrations/0022_auto_20181024_0933.py diff --git a/dispatch/migrations/0022_auto_20181024_0933.py b/dispatch/migrations/0022_auto_20181024_0933.py new file mode 100644 index 000000000..64cd8d5e1 --- /dev/null +++ b/dispatch/migrations/0022_auto_20181024_0933.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-10-24 16:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dispatch', '0021_podcast_fields'), + ] + + operations = [ + migrations.AlterField( + model_name='topic', + name='slug', + field=models.SlugField(max_length=255, unique=True), + ), + ] diff --git a/dispatch/static/manager/src/js/actions/ZonesActions.js b/dispatch/static/manager/src/js/actions/ZonesActions.js index 11460b66d..01e992314 100644 --- a/dispatch/static/manager/src/js/actions/ZonesActions.js +++ b/dispatch/static/manager/src/js/actions/ZonesActions.js @@ -9,89 +9,11 @@ import * as types from '../constants/ActionTypes' import { zoneSchema, widgetSchema, - articleSchema, - imageSchema, - eventSchema, - topicSchema, - podcastEpisodeSchema, } from '../constants/Schemas' -function normalizeZoneData(field, data) { - switch (field.type) { - case 'article': - return field.many ? normalize(data, arrayOf(articleSchema)) : normalize(data, articleSchema) - case 'image': - return field.many ? normalize(data, arrayOf(imageSchema)) : normalize(data, imageSchema) - case 'event': - return field.many ? normalize(data, arrayOf(eventSchema)) : normalize(data, eventSchema) - case 'topic': - return field.many ? normalize(data, arrayOf(topicSchema)) : normalize(data, topicSchema) - case 'podcast': - return field.many ? normalize(data, arrayOf(podcastEpisodeSchema)) : normalize(data, podcastEpisodeSchema) - case 'widget': - return normalizeZone(data, true) - default: - return { - result: data, - entities: {} - } - } -} +import * as fields from '../components/fields' -function normalizeZone(zone, isNestedWidget=false) { - const fields = R.path(['widget', 'fields'], zone) || [] - - let fieldEntities = {} - - fields.forEach(field => { - if (!zone.data || !zone.data[field.name]) { - return - } - - const normalizedData = normalizeZoneData(field, zone.data[field.name]) - - fieldEntities = R.mergeWith( - R.merge, - fieldEntities, - normalizedData.entities - ) - - if (field.type != 'widget') { - zone.data[field.name] = normalizedData.result - } - }) - - let result = !isNestedWidget ? normalize(zone, zoneSchema) : {} - - result.entities = R.mergeWith( - R.merge, - result.entities, - fieldEntities - ) - - return result -} - -function normalizeZones(zones) { - let results = [] - let entities = {} - - zones.forEach(zone => { - const normalizedData = normalizeZone(zone) - results.push(normalizedData.result) - - entities = R.mergeWith( - R.merge, - entities, - normalizedData.entities - ) - }) - - return { - result: results, - entities: entities - } -} +const schemaMap = initSchemaMap() export function list(token, query) { return { @@ -160,3 +82,83 @@ export function listWidgets(token, zoneId) { })) } } + +function normalizeZoneData(field, data) { + if (schemaMap.has(field.type)) { + const schema = schemaMap.get(field.type) + return field.many ? normalize(data, arrayOf(schema)) : normalize(data, schema) + } else { + return { + result: data, + entities: {} + } + } +} + +function normalizeZone(zone, isNestedWidget=false) { + const fields = R.path(['widget', 'fields'], zone) || [] + + let fieldEntities = {} + + fields.forEach(field => { + if (!zone.data || !zone.data[field.name]) { + return + } + + const normalizedData = normalizeZoneData(field, zone.data[field.name]) + + fieldEntities = R.mergeWith( + R.merge, + fieldEntities, + normalizedData.entities + ) + + if (field.type != 'widget') { + zone.data[field.name] = normalizedData.result + } + }) + + let result = !isNestedWidget ? normalize(zone, zoneSchema) : {} + + result.entities = R.mergeWith( + R.merge, + result.entities, + fieldEntities + ) + + return result +} + +function normalizeZones(zones) { + let results = [] + let entities = {} + + zones.forEach(zone => { + const normalizedData = normalizeZone(zone) + results.push(normalizedData.result) + + entities = R.mergeWith( + R.merge, + entities, + normalizedData.entities + ) + }) + + return { + result: results, + entities: entities + } +} + +function initSchemaMap() { + var schemaMap = new Map() + + for (let fieldType in fields) { + const field = fields[fieldType] + if (field.schema) { + schemaMap.set(field.type, field.schema) + } + } + + return schemaMap +} \ No newline at end of file diff --git a/dispatch/static/manager/src/js/components/ZoneEditor/index.js b/dispatch/static/manager/src/js/components/ZoneEditor/index.js index b0628a496..b225aa906 100644 --- a/dispatch/static/manager/src/js/components/ZoneEditor/index.js +++ b/dispatch/static/manager/src/js/components/ZoneEditor/index.js @@ -5,12 +5,11 @@ import DocumentTitle from 'react-document-title' import * as zonesActions from '../../actions/ZonesActions' -import ListItemToolbar from '../ItemEditor/ListItemToolbar' -import Panel from '../Panel' -import WidgetSelectInput from '../inputs/selects/WidgetSelectInput' import * as Form from '../Form' - +import Panel from '../Panel' import FieldGroup from '../fields/FieldGroup' +import ListItemToolbar from '../ItemEditor/ListItemToolbar' +import WidgetSelectInput from '../inputs/selects/WidgetSelectInput' class ZoneEditorComponent extends React.Component { @@ -35,24 +34,10 @@ class ZoneEditorComponent extends React.Component { return zone } - function processWidget(widget, fields) { - if (!fields) { - return widget - } - - fields.forEach((field) => { - if (field.type == 'widget') { - const newWidget = R.path(['data', field.name], widget) - if (newWidget) { - widget.data[field.name] = processWidget(newWidget, R.path(['widget', 'fields'], newWidget)) - widget = R.dissocPath(['data', field.name, 'widget'], widget) - } - } - }) - return widget - } - - return processWidget(zone, R.prop('fields', this.props.widget || {})) + return processNestedWidgets( + zone, + R.prop('fields', this.props.widget || {}) + ) } saveZone() { @@ -112,6 +97,30 @@ class ZoneEditorComponent extends React.Component { } } +function processNestedWidgets(widget, fields) { + if (!fields) { + return widget + } + + fields.forEach((field) => { + if (field.type == 'widget') { + const newWidget = R.path(['data', field.name], widget) + + if (newWidget) { + + widget.data[field.name] = processNestedWidgets( + newWidget, + R.path(['widget', 'fields'], newWidget) + ) + + widget = R.dissocPath(['data', field.name, 'widget'], widget) + } + } + }) + + return widget +} + const mapStateToProps = (state) => { const zone = state.app.entities.local.zones[state.app.zones.single.id] const widget = zone ? state.app.entities.widgets[zone.widget] : null diff --git a/dispatch/static/manager/src/js/components/fields/ArticleField.js b/dispatch/static/manager/src/js/components/fields/ArticleField.js index c2c0e1a7c..52c5ee1a9 100644 --- a/dispatch/static/manager/src/js/components/fields/ArticleField.js +++ b/dispatch/static/manager/src/js/components/fields/ArticleField.js @@ -1,8 +1,10 @@ import React from 'react' +import { articleSchema } from '../../constants/Schemas' + import ArticleSelectInput from '../inputs/selects/ArticleSelectInput' -export default function ArticleField(props) { +function ArticleField(props) { return ( props.onChange(selected)} /> ) } + +ArticleField.type = 'article' +ArticleField.schema = articleSchema + +export default ArticleField \ No newline at end of file diff --git a/dispatch/static/manager/src/js/components/fields/EventField.js b/dispatch/static/manager/src/js/components/fields/EventField.js index d844268f3..33fb4bdc5 100644 --- a/dispatch/static/manager/src/js/components/fields/EventField.js +++ b/dispatch/static/manager/src/js/components/fields/EventField.js @@ -1,8 +1,10 @@ import React from 'react' +import { eventSchema } from '../../constants/Schemas' + import EventSelectInput from '../inputs/selects/EventSelectInput' -export default function EventField(props) { +function EventField(props) { return ( props.onChange(selected)} /> ) } + +EventField.type = 'event' +EventField.schema = eventSchema + +export default EventField \ No newline at end of file diff --git a/dispatch/static/manager/src/js/components/fields/ImageField.js b/dispatch/static/manager/src/js/components/fields/ImageField.js index 2ec533856..978fe1e9f 100644 --- a/dispatch/static/manager/src/js/components/fields/ImageField.js +++ b/dispatch/static/manager/src/js/components/fields/ImageField.js @@ -1,8 +1,10 @@ import React from 'react' +import { imageSchema } from '../../constants/Schemas' + import ImageInput from '../inputs/ImageInput' -export default function ImageField(props) { +function ImageField(props) { return ( props.onChange(selected)} /> ) } + +ImageField.type = 'image' +ImageField.schema = imageSchema + +export default ImageField \ No newline at end of file diff --git a/dispatch/static/manager/src/js/components/fields/PodcastField.js b/dispatch/static/manager/src/js/components/fields/PodcastField.js index b2503c5d3..6574bbb68 100644 --- a/dispatch/static/manager/src/js/components/fields/PodcastField.js +++ b/dispatch/static/manager/src/js/components/fields/PodcastField.js @@ -1,8 +1,10 @@ import React from 'react' +import { podcastEpisodeSchema } from '../../constants/Schemas' + import PodcastEpisodeSelectInput from '../inputs/selects/PodcastEpisodeSelectInput' -export default function PodcastField(props) { +function PodcastField(props) { return ( props.onChange(selected)} /> ) } + +PodcastField.type = 'podcast' +PodcastField.schema = podcastEpisodeSchema + +export default PodcastField \ No newline at end of file diff --git a/dispatch/static/manager/src/js/components/fields/PollField.js b/dispatch/static/manager/src/js/components/fields/PollField.js index 20f0d3a85..1c8022d61 100644 --- a/dispatch/static/manager/src/js/components/fields/PollField.js +++ b/dispatch/static/manager/src/js/components/fields/PollField.js @@ -1,8 +1,10 @@ import React from 'react' +import { pollSchema } from '../../constants/Schemas' + import PollSelectInput from '../inputs/selects/PollSelectInput' -export default function PollField(props) { +function PollField(props) { return ( props.onChange(selected)} /> ) } + +PollField.type = 'poll' +PollField.schema = pollSchema + +export default PollField \ No newline at end of file diff --git a/dispatch/static/manager/src/js/components/fields/TopicField.js b/dispatch/static/manager/src/js/components/fields/TopicField.js index b2f6008bd..7ab26ea10 100644 --- a/dispatch/static/manager/src/js/components/fields/TopicField.js +++ b/dispatch/static/manager/src/js/components/fields/TopicField.js @@ -1,8 +1,10 @@ import React from 'react' +import { topicSchema } from '../../constants/Schemas' + import TopicSelectInput from '../inputs/selects/TopicSelectInput' -export default function TopicField(props) { +function TopicField(props) { return ( props.onChange(selected)} /> ) } + +TopicField.type = 'topic' +TopicField.schema = topicSchema + +export default TopicField \ No newline at end of file diff --git a/dispatch/static/manager/src/js/components/fields/WidgetField.js b/dispatch/static/manager/src/js/components/fields/WidgetField.js index 4c4d7b4c4..cb2443a71 100644 --- a/dispatch/static/manager/src/js/components/fields/WidgetField.js +++ b/dispatch/static/manager/src/js/components/fields/WidgetField.js @@ -1,11 +1,12 @@ import React from 'react' import R from 'ramda' -import WidgetSelectInput from '../inputs/selects/WidgetSelectInput' +import { widgetSchema } from '../../constants/Schemas' import FieldGroup from './FieldGroup' +import WidgetSelectInput from '../inputs/selects/WidgetSelectInput' -export default class WidgetFieldComponent extends React.Component { +class WidgetField extends React.Component { handleWidgetChange(widgetId) { if (!widgetId) { // Set data to null when removing widget @@ -68,7 +69,12 @@ export default class WidgetFieldComponent extends React.Component { } } -WidgetFieldComponent.defaultProps = { +WidgetField.defaultProps = { data: {}, field: {} } + +WidgetField.type = 'widget' +WidgetField.schema = widgetSchema + +export default WidgetField \ No newline at end of file diff --git a/dispatch/static/manager/src/js/components/fields/index.js b/dispatch/static/manager/src/js/components/fields/index.js index fcc86bcd5..32dcd0e36 100644 --- a/dispatch/static/manager/src/js/components/fields/index.js +++ b/dispatch/static/manager/src/js/components/fields/index.js @@ -1,31 +1,31 @@ -import CharField from './CharField' -import TextField from './TextField' import ArticleField from './ArticleField' -import ImageField from './ImageField' -import EventField from './EventField' +import BoolField from './BoolField' +import CharField from './CharField' import DateTimeField from './DateTimeField' +import EventField from './EventField' +import ImageField from './ImageField' +import InstructionField from './InstructionField' import IntegerField from './IntegerField' -import BoolField from './BoolField' -import SelectField from './SelectField' -import WidgetField from './WidgetField' -import PollField from './PollField' import PodcastField from './PodcastField' -import InstructionField from './InstructionField' +import PollField from './PollField' +import SelectField from './SelectField' +import TextField from './TextField' import TopicField from './TopicField' +import WidgetField from './WidgetField' export { - CharField as char, - TextField as text, ArticleField as article, - ImageField as image, - EventField as event, + BoolField as bool, + CharField as char, DateTimeField as datetime, + EventField as event, + ImageField as image, + InstructionField as instruction, IntegerField as integer, - BoolField as bool, - SelectField as select, - WidgetField as widget, + PodcastField as podcast, PollField as poll, - InstructionField as instruction, + SelectField as select, + TextField as text, TopicField as topic, - PodcastField as podcast, + WidgetField as widget, } From 1b3ed645e73a2b3a010c5c5a5eb431a174fff184 Mon Sep 17 00:00:00 2001 From: Peter Siemens Date: Fri, 26 Oct 2018 00:41:41 -0700 Subject: [PATCH 2/2] Remove slug migration --- .../migrations/0022_auto_20181024_0933.py | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 dispatch/migrations/0022_auto_20181024_0933.py diff --git a/dispatch/migrations/0022_auto_20181024_0933.py b/dispatch/migrations/0022_auto_20181024_0933.py deleted file mode 100644 index 64cd8d5e1..000000000 --- a/dispatch/migrations/0022_auto_20181024_0933.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-10-24 16:33 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dispatch', '0021_podcast_fields'), - ] - - operations = [ - migrations.AlterField( - model_name='topic', - name='slug', - field=models.SlugField(max_length=255, unique=True), - ), - ]