From 9be28b4322a629b8fe7c35e8cdc42eb413c6d84a Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Fri, 3 Jul 2015 11:55:40 -0400 Subject: Protect admin root path with authentication as well Fixes #5 --- app/node_modules/okadminview/index.js | 4 +++- package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/node_modules/okadminview/index.js b/app/node_modules/okadminview/index.js index 2a0fcd5..f6a7cdb 100644 --- a/app/node_modules/okadminview/index.js +++ b/app/node_modules/okadminview/index.js @@ -118,7 +118,9 @@ function OKAdminView(options) { // This should really be mounted on the router, but can't be due to // https://github.com/jaredhanson/passport-http/pull/16 app.use('/admin/', passport.initialize()); - app.all('/admin/:path*', passport.authenticate('digest', {session: false})); + app.all('/admin/(:path*)?', passport.authenticate('digest', { + session: false + })); router.get('/', function readIndex(req, res, next) { fetchIndexTemplateData(meta, indexQueries).then(function(data) { diff --git a/package.json b/package.json index 50ec3e6..1b6b909 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "okcms", - "version": "0.1.10", + "version": "0.1.11", "description": "great", "main": "app/index.js", "scripts": { -- cgit v1.2.3-70-g09d2 From 42fe17f3e52be163a506bf9a3953aa8adb5fd64f Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Thu, 9 Jul 2015 16:58:02 -0400 Subject: Implement groupBy queries + admin interface --- app/index.js | 14 ++++--- app/node_modules/okadminview/index.js | 42 +++++++++------------ app/node_modules/okquery/index.js | 43 ++++++++++++++++++++- app/node_modules/okview/index.js | 26 +++++++------ themes/okadmin/public/js/app.js | 12 +++--- themes/okadmin/templates/index.liquid | 71 +++++++++++++++++++++++++---------- 6 files changed, 140 insertions(+), 68 deletions(-) diff --git a/app/index.js b/app/index.js index 1c77728..d514f10 100644 --- a/app/index.js +++ b/app/index.js @@ -60,6 +60,7 @@ function OKCMS(options) { '/': { template: 'index' } }; var serviceConfig = options.services || {}; + var adminConfig = options.admin || {} var templateProvider = this._templateProvider = new OKTemplate({ root: templateRoot, @@ -84,7 +85,7 @@ function OKCMS(options) { this._createViews(viewConfig, db, meta, resourceCache, templateProvider, errorHandler); var adminViews = this._adminViews = - this._createAdminViews(adminPath, app, express, resourceConfig, + this._createAdminViews(adminConfig, adminPath, app, express, resourceConfig, resourceCache, adminTemplateProvider, adminMeta, errorHandler); @@ -194,9 +195,8 @@ OKCMS.prototype._createViews = function(viewConfig, db, } }; -OKCMS.prototype._createAdminViews = function(path, app, express, - resourceConfig, resourceCache, templateProvider, meta, - errorHandler) { +OKCMS.prototype._createAdminViews = function(adminConfig, path, app, express, + resourceConfig, resourceCache, templateProvider, meta, errorHandler) { var views = {}; var withTrail = withTrailingSlash(path); var withoutTrail = withoutTrailingSlash(path); @@ -221,7 +221,8 @@ OKCMS.prototype._createAdminViews = function(path, app, express, resourceCache: resourceCache, templateProvider: templateProvider, meta: meta, - errorHandler: errorHandler + errorHandler: errorHandler, + dashboardConfig: adminConfig.dashboard || {} }); return views; }; @@ -243,7 +244,8 @@ OKCMS.prototype._createQueries = function(queryConfig, resourceCache) { query: query, as: config.as, sortBy: config.sortBy, - descending: config.descending + descending: config.descending, + groupBy: config.groupBy }); }); }; diff --git a/app/node_modules/okadminview/index.js b/app/node_modules/okadminview/index.js index f6a7cdb..924f5a5 100644 --- a/app/node_modules/okadminview/index.js +++ b/app/node_modules/okadminview/index.js @@ -40,6 +40,8 @@ function OKAdminView(options) { if (!options.errorHandler) throw new Error('No error handler provided to OKAdminView'); + var dashboardConfig = options.dashboardConfig || {} + var dashboardResourceConfig = dashboardConfig.resources || {} var app = options.app; var express = options.express; var meta = options.meta; @@ -79,10 +81,12 @@ function OKAdminView(options) { var id = resource.getID(staticData); // Check to see if there's a more specific instance resource = resourceCache.get(type, id) || resource; + var resourceOptions = dashboardResourceConfig[type] || {} + var groupBy = resourceOptions.groupBy if (resource.bound) { - return OKQuery({resource: resource});; + return OKQuery({resource: resource, groupBy: groupBy}) } else { - return OKQuery({resource: resource, query: config.query}) + return OKQuery({resource: resource, query: config.query, groupBy: groupBy}) } }); @@ -123,7 +127,7 @@ function OKAdminView(options) { })); router.get('/', function readIndex(req, res, next) { - fetchIndexTemplateData(meta, indexQueries).then(function(data) { + fetchIndexTemplateData(meta, indexQueries, dashboardConfig).then(function(data) { view.renderIndex(req, res, assign(data, { success: req.flash('success'), errors: req.flash('errors') @@ -316,7 +320,9 @@ OKAdminView.prototype.renderResourceNew = function(req, res, data) { /** * Annotate template data with schema info */ -function fetchIndexTemplateData(meta, queries) { +function fetchIndexTemplateData(meta, queries, dashboardConfig) { + var resourceConfig = dashboardConfig.resources + return Q.promise(function(resolve, reject) { Q.all(queries.map(function(query) { return query.get(); @@ -327,27 +333,15 @@ function fetchIndexTemplateData(meta, queries) { var resource = queries[i].resource; // We want the raw object spec var spec = resource.spec; - var key = pluralize(resource.type); - if (!cache[key]) { - cache[key] = { - type: resource.type, - spec: spec, - data: [] - }; - } - - if (result.length) { - result.forEach(addData) - } else { - addData(result); + var dashConf = resourceConfig[resource.type] || {} + var groupBy = dashConf.groupBy + var key = pluralize(resource.type) + cache[key] = { + type: resource.type, + spec: spec, + data: result, + groupBy: groupBy } - - function addData(data) { - // Report id to template under standard name - data.id = resource.getID(data); - cache[key].data.push(data); - } - return cache; }, {}); diff --git a/app/node_modules/okquery/index.js b/app/node_modules/okquery/index.js index 4051f95..09d867d 100644 --- a/app/node_modules/okquery/index.js +++ b/app/node_modules/okquery/index.js @@ -43,10 +43,17 @@ function OKQuery(options) { enumerable: true }); + Object.defineProperty(this, 'groupBy', { + value: options.groupBy, + writable: false, + enumerable: true + }) + this.get = createQuery(resource, query, { default: options.default, sortField: sortField, - descending : descending + descending : descending, + groupBy: options.groupBy }); } @@ -66,6 +73,9 @@ function createQuery(resource, query, options) { if (options.default) { query = withDefault(query, options.default); } + if (options.groupBy) { + query = withGrouping(query, options.groupBy) + } return query; } @@ -124,6 +134,37 @@ function queryBound(resource) { }; } +/** + * Transform the query such that the results are grouped by the + * given field + */ +function withGrouping(queryFn, groupField) { + return function() { + return Q.Promise(function(resolve, reject) { + queryFn().then(function(data) { + data = data || [] + if (typeof data.length === 'undefined') { + data = [data] + } + var result = {} + result[groupField] = data.reduce(reduceToGroups, {}) + resolve(result) + }, reject) + }) + } + + function reduceToGroups(acc, data) { + var groupName = data[groupField] + if (groupName) { + if (!acc[groupName]) { + acc[groupName] = [] + } + acc[groupName].push(data) + } + return acc + } +} + function withDefault(queryFn, resultDefault) { return function() { return Q.Promise(function(resolve, reject) { diff --git a/app/node_modules/okview/index.js b/app/node_modules/okview/index.js index fba1c18..5f99d59 100644 --- a/app/node_modules/okview/index.js +++ b/app/node_modules/okview/index.js @@ -135,25 +135,28 @@ function fetchTemplateData(meta, queries, id) { if (!result) { return cache; } - var resource = queries[i].resource; - var type = queries[i].as || queries[i].type; + var query = queries[i] + var resource = query.resource; + var type = query.as || query.type; var manyResult = isarray(result); - // Inform template of ID in generic field - if (manyResult) { - result = result.map(function(data) { - return assign({}, data, {id: resource.getID(data)}) - }); - } else { - result = assign({}, result, {id: resource.getID(result)}); - } + var groupBy = query.groupBy // If we have a lot of results for a certain type, // we pluralize the key and yield an array of results - if (cache[type] || manyResult) { + if (cache[type] || manyResult || groupBy) { var plural = pluralize(type); delete cache[type]; cache[plural] = []; + // Pluralize grouped field + if (query.groupBy) { + result = Object.keys(result).reduce(function(acc, key) { + acc[pluralize(key)] = result[key] + return acc + }, {}) + } if (manyResult) { cache[plural] = cache[plural].concat(result); + } else if (groupBy) { + cache[plural] = result } else { cache[plural].push(result); } @@ -162,7 +165,6 @@ function fetchTemplateData(meta, queries, id) { } return cache; }, {meta: meta}); - resolve(normalized); } }).fail(reject); diff --git a/themes/okadmin/public/js/app.js b/themes/okadmin/public/js/app.js index 4be0afc..8a827ff 100644 --- a/themes/okadmin/public/js/app.js +++ b/themes/okadmin/public/js/app.js @@ -1,5 +1,5 @@ var OKAdmin = function(){ - + // initialize our multi-image uploader with an element and a template $(".group.image-list").each(function(){ var parent = this @@ -79,7 +79,7 @@ var OKAdmin = function(){ // make the region sortable with drag-and-drop $(".media-list ol, .image-list ol").sortable() $(".media-list ol, .image-list ol").disableSelection() - + // populate a video field with info from our url parser var last_url $(".video .url").on("focus", function(){ @@ -201,7 +201,7 @@ var OKAdmin = function(){ }) }) }) - + // delete individual records $("#delete_form").submit(function(e){ if (confirm("Are you sure you want to delete this record?")) { @@ -213,9 +213,9 @@ var OKAdmin = function(){ }) // reorder items in categories - $(".resource-category").on("click", ".edit-btn", function(e) { + $(".resource-category:not(.grouped)").on("click", ".edit-btn", function(e) { e.preventDefault(); - var $parent = $(e.delegateTarget); + var $parent = $(e.delegateTarget) var $editBtn = $parent.find(".edit-btn"); var $cancelBtn = $parent.find(".cancel-btn"); var $saveBtn = $parent.find(".save-btn"); @@ -245,7 +245,7 @@ var OKAdmin = function(){ }); // save new category order - $(".resource-category").on("submit", "form", function(e) { + $(".resource-category.root").on("submit", "form", function(e) { var $parent = $(e.delegateTarget); $parent.find(".resource-input").each(function(index) { var $input = $(this); diff --git a/themes/okadmin/templates/index.liquid b/themes/okadmin/templates/index.liquid index e9ad538..7af84b6 100644 --- a/themes/okadmin/templates/index.liquid +++ b/themes/okadmin/templates/index.liquid @@ -7,33 +7,66 @@ {% assign name = pair[0] %} {% assign resource = pair[1] %} -
+

{{name | capitalize}}

- -
    - {% for data in resource.data %} -
  1. - {{data.title}} - -
  2. + + {% if resource.groupBy %} + {% assign i = 0 %} + {% for pair in resource.data[resource.groupBy] %} + {% assign group = pair[0] %} + {% assign members = pair[1] %} +
    +
    +

    {{group | capitalize}}

    +
    +
      + {% for data in members %} +
    1. + {{data.title}} + +
    2. + {% assign i = i | plus: 1 %} + {% endfor %} +
    + +
    {% endfor %} + {% else %} +
      + {% for data in resource.data %} +
    1. + {{data.title}} + +
    2. + {% endfor %}
    - + + {% endif %}
- {% endfor %}
-- cgit v1.2.3-70-g09d2 From c2761e84bb496c401c677dcdc768c142cb22a326 Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Thu, 9 Jul 2015 18:13:00 -0400 Subject: Allow custom resource sorting on admin page --- app/node_modules/okadminview/index.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/app/node_modules/okadminview/index.js b/app/node_modules/okadminview/index.js index 924f5a5..273d541 100644 --- a/app/node_modules/okadminview/index.js +++ b/app/node_modules/okadminview/index.js @@ -81,12 +81,25 @@ function OKAdminView(options) { var id = resource.getID(staticData); // Check to see if there's a more specific instance resource = resourceCache.get(type, id) || resource; - var resourceOptions = dashboardResourceConfig[type] || {} - var groupBy = resourceOptions.groupBy + var dashConf = dashboardResourceConfig[type] || {} + var groupBy = dashConf.groupBy + var sortBy = dashConf.sortBy + var descending = dashConf.descending if (resource.bound) { - return OKQuery({resource: resource, groupBy: groupBy}) + return OKQuery({ + resource: resource, + groupBy: groupBy, + sortBy: sortBy, + descending: descending + }) } else { - return OKQuery({resource: resource, query: config.query, groupBy: groupBy}) + return OKQuery({ + resource: resource, + query: config.query, + groupBy: groupBy, + sortBy: sortBy, + descending: descending + }) } }); @@ -321,7 +334,7 @@ OKAdminView.prototype.renderResourceNew = function(req, res, data) { * Annotate template data with schema info */ function fetchIndexTemplateData(meta, queries, dashboardConfig) { - var resourceConfig = dashboardConfig.resources + var resourceConfig = dashboardConfig.resources || {} return Q.promise(function(resolve, reject) { Q.all(queries.map(function(query) { -- cgit v1.2.3-70-g09d2 From 206b101481bad2454410bcb5a73b6c46816d43ef Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Thu, 9 Jul 2015 18:14:02 -0400 Subject: Version++ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1b6b909..f91a986 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "okcms", - "version": "0.1.11", + "version": "0.1.12", "description": "great", "main": "app/index.js", "scripts": { -- cgit v1.2.3-70-g09d2 From fb5605bdee9f527ffeefd0b59af01534b4d3b8ac Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Fri, 10 Jul 2015 15:20:03 -0400 Subject: Strikethrough disabled resources on admin index --- themes/okadmin/templates/index.liquid | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/themes/okadmin/templates/index.liquid b/themes/okadmin/templates/index.liquid index 7af84b6..ae30bc8 100644 --- a/themes/okadmin/templates/index.liquid +++ b/themes/okadmin/templates/index.liquid @@ -27,7 +27,9 @@
    {% for data in members %}
  1. - {{data.title}} + {% if data.disabled == 'true' %} {% endif %} + {{data.title}} + {% if data.disabled == 'true' %} {% endif %}
  2. @@ -49,7 +51,9 @@
      {% for data in resource.data %}
    1. - {{data.title}} + {% if data.disabled == 'true' %} {% endif %} + {{data.title}} + {% if data.disabled == 'true' %} {% endif %}
    2. -- cgit v1.2.3-70-g09d2 From 7249dc10568091194a77990513fa2b77fdbd088a Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Fri, 10 Jul 2015 17:31:40 -0400 Subject: Implement basic foreign key support --- app/index.js | 23 ++++++ app/node_modules/okadminview/index.js | 100 ++++++++++++++++++------ app/node_modules/okquery/index.js | 1 + app/node_modules/okresource/index.js | 66 ++++++++++++++++ app/node_modules/okschema/index.js | 4 + themes/okadmin/templates/partials/inputs.liquid | 2 +- 6 files changed, 169 insertions(+), 27 deletions(-) diff --git a/app/index.js b/app/index.js index d514f10..2507dd2 100644 --- a/app/index.js +++ b/app/index.js @@ -78,6 +78,7 @@ function OKCMS(options) { }); var resourceCache = this._resourceCache = this._createResources(resourceConfig, db, schemas); + this._resolveForeignKeys(resourceCache) var errorHandler = createErrorHandlerProducer( templateProvider, adminTemplateProvider, debug); // Create view instances from config @@ -154,6 +155,21 @@ OKCMS.prototype._createResources = function(resourceConfig, db, schemaCache) { return ResourceCache(resources); }; +OKCMS.prototype._resolveForeignKeys = function(resourceCache) { + resourceCache.forEach(function(resource) { + Object.keys(resource.foreignKeys).forEach(function(field) { + var foreignKeyType = resource.foreignKeys[field] + var keyedResource = resourceCache.get(foreignKeyType) + if (!keyedResource) { + throw new Error(format( + "Foreign key field '%s' in '%s' resource references unknown" + + "resource of type '%s'", field, resource.type, foreignKeyType)) + } + resource._linkForeignKey(field, resourceCache.get(foreignKeyType)) + }) + }) +} + OKCMS.prototype._createViews = function(viewConfig, db, meta, resourceCache, templateProvider, errorHandler) { viewConfig = viewConfig || {}; @@ -279,6 +295,13 @@ ResourceCache.prototype.get = function(type, id) { } }; +ResourceCache.prototype.forEach = function(cb) { + cb = cb || function() {} + Object.keys(this._cache).forEach(function(key) { + cb(this._cache[key]) + }.bind(this)) +} + /** * Higher order function implementing customizable error handling */ diff --git a/app/node_modules/okadminview/index.js b/app/node_modules/okadminview/index.js index 273d541..06656dc 100644 --- a/app/node_modules/okadminview/index.js +++ b/app/node_modules/okadminview/index.js @@ -154,11 +154,12 @@ function OKAdminView(options) { if (!resource) { error(req, res, 404)(new Error('No such resource ' + type)); } else { - var templateData = transformData(meta, resource, {}); - view.renderResourceNew(req, res, assign(templateData, { - success: req.flash('success'), - errors: req.flash('errors'), - })); + fetchNewTemplateData(meta, resource, transformData).then(function(data) { + view.renderResourceNew(req, res, assign(data, { + success: req.flash('success'), + errors: req.flash('errors'), + })) + }).fail(error(req, res, 500)) } }); @@ -280,11 +281,11 @@ function OKAdminView(options) { /** * Yields formatted template data for a single resource */ -function transformData(meta, resource, data) { +function transformData(meta, spec, resource, data) { meta = meta || {}; resource = resource || {}; data = data || {}; - var spec = Object.keys(resource.spec).reduce(function(cache, prop) { + var spec = Object.keys(spec).reduce(function(cache, prop) { var value = data[prop]; var propSpec = cache[prop]; // Decorate spec with actual resource values @@ -294,7 +295,7 @@ function transformData(meta, resource, data) { propSpec.hidden = true; } return cache; - }, resource.spec); + }, spec); return { meta: meta, resource: { @@ -340,47 +341,94 @@ function fetchIndexTemplateData(meta, queries, dashboardConfig) { Q.all(queries.map(function(query) { return query.get(); })).then(function(results) { - var resources = results.reduce(function(cache, result, i) { - if (!result) - return cache; + var templateData = results.reduce(function(acc, result, i) { var resource = queries[i].resource; // We want the raw object spec var spec = resource.spec; var dashConf = resourceConfig[resource.type] || {} var groupBy = dashConf.groupBy var key = pluralize(resource.type) - cache[key] = { + acc[key] = { type: resource.type, spec: spec, data: result, groupBy: groupBy } - return cache; - }, {}); - + return acc + }, {}) resolve({ meta: meta, - resources: resources - }); - }).fail(reject); + resources: templateData + }) + }).fail(reject) }); } +function fetchNewTemplateData(meta, resource, transformFn) { + return Q.promise(function(resolve, reject) { + if (!resource.hasForeignKey) { + done({spec: resource.spec, resource: resource}) + } else { + fetchForeignKeyOptions(resource).then(done).fail(reject) + } + + function done(results) { + resolve(transformFn(meta, results.spec, results.resource, {})) + } + }) +} + /** * Annotate template data with schema info */ -function fetchResourceTemplateData(meta, query, fn) { - fn = fn || function(m, r, d) { return {meta: m, resource: d}; }; +function fetchResourceTemplateData(meta, query, transformFn) { return Q.promise(function(resolve, reject) { query.get().then(function(data) { - if (!data) { - reject(new Error('No resource data')); + if (!data) + return reject(new Error('No resource data')) + + var resource = query.resource + + if (resource.hasForeignKey) { + fetchForeignKeyOptions(resource).then(done).fail(reject) } else { - var resource = query.resource; - resolve(fn(meta, resource, data)); + done({spec: resource.spec, resource: resource}) } - }).fail(reject); - }); + + function done(results) { + resolve(transformFn(meta, results.spec, results.resource, data)) + } + }).fail(reject) + }) +} + +function fetchForeignKeyOptions(resource) { + var promises = Object.keys(resource.foreignKeys) + .map(fetchOptionsForKey) + var spec = resource.spec + + return Q.all(promises).then(done) + + function done() { + return Q.promise(function(resolve, reject) { + resolve({spec: spec, resource: resource}) + }) + } + + function fetchOptionsForKey(field) { + var relatedResourceType = resource.foreignKeys[field] + return resource.related(relatedResourceType).then(fillOptions) + + function fillOptions(results) { + return Q.promise(function(resolve, reject) { + spec[field].options = results.map(function(result) { + return result.id + }) + resolve() + }) + } + } } + module.exports = OKAdminView; diff --git a/app/node_modules/okquery/index.js b/app/node_modules/okquery/index.js index 09d867d..d4cb905 100644 --- a/app/node_modules/okquery/index.js +++ b/app/node_modules/okquery/index.js @@ -23,6 +23,7 @@ function OKQuery(options) { // Queries are ordered by index by default var sortField = options.sortBy || '__index'; + // TODO Make descending by default var descending = options.descending || false; Object.defineProperty(this, 'resource', { diff --git a/app/node_modules/okresource/index.js b/app/node_modules/okresource/index.js index c3f9adb..c1f2509 100644 --- a/app/node_modules/okresource/index.js +++ b/app/node_modules/okresource/index.js @@ -1,3 +1,4 @@ +var format = require('util').format var assign = require('object-assign'); var cloneDeep = require('lodash.clonedeep'); var Q = require('q'); @@ -22,10 +23,36 @@ function OKResource(options) { var spec = schema.spec; var type = options.type; + var hasForeignKey = false this._db = options.db; this._schema = schema; + var foreignKeys = Object.keys(spec).reduce(function(acc, field) { + var fieldSpec = spec[field] + if (fieldSpec.type === 'foreign-key') { + hasForeignKey = true + acc[field] = fieldSpec.key + } + return acc + }, {}) + + // Will store references to other resources referenced via foreign keys + this._foreignKeyedResources = {} + // Define properties which are part of the API + + // Should be treated as read-only + Object.defineProperty(this, 'foreignKeys', { + get: function() { + return foreignKeys + } + }) + + Object.defineProperty(this, 'hasForeignKey', { + get: function() { + return hasForeignKey + } + }) Object.defineProperty(this, 'spec', { get: function() { @@ -49,6 +76,30 @@ function OKResource(options) { }); } +OKResource.prototype._linkForeignKey = function(field, resource) { + this._foreignKeyedResources[field] = resource +} + +/** + * Fetch all related resources for the given field + */ +OKResource.prototype.related = function(field) { + var resource = this._foreignKeyedResources[field] + return Q.promise(function(resolve, reject) { + if (!resource) { + return error(reject, new Error(format( + "No related resource for field '%s'", field))) + } + resource.all().then(resolve).fail(reject) + }) + + function error(reject, err) { + setTimeout(function() { + reject(err) + }, 0) + } +} + /** * Throws an error if data does not conform to schema */ @@ -180,6 +231,9 @@ OKResource.prototype.instance = function(options) { }); }; +/** + * TODO This class is such bullshit. Refactor out + */ function OKResourceInstance(resource, options) { if (!(this instanceof OKResourceInstance)) return new OKResourceInstance(options); // Only support static data instances for now @@ -279,6 +333,18 @@ function OKResourceInstance(resource, options) { resource.assertValid(data); }; + Object.defineProperty(this, 'foreignKeys', { + get: function() { + return [] + } + }) + + Object.defineProperty(this, 'hasForeignKey', { + get: function() { + return false + } + }) + Object.defineProperty(this, 'parent', { value: resource, writable: false, diff --git a/app/node_modules/okschema/index.js b/app/node_modules/okschema/index.js index 89b59cc..330ad6b 100644 --- a/app/node_modules/okschema/index.js +++ b/app/node_modules/okschema/index.js @@ -73,6 +73,10 @@ var types = { 'flag': { parent: 'string', assertValid: function(spec, value) {} + }, + 'foreign-key': { + parent: 'enum', + assertValid: function(spec, value) {} } } diff --git a/themes/okadmin/templates/partials/inputs.liquid b/themes/okadmin/templates/partials/inputs.liquid index 9c1a26b..c6efc68 100644 --- a/themes/okadmin/templates/partials/inputs.liquid +++ b/themes/okadmin/templates/partials/inputs.liquid @@ -17,7 +17,7 @@ - {% elsif type == 'enum' %} + {% elsif type == 'enum' or type == 'foreign-key' %}