Unable to see fields if there are both a relation on the same table

Expected behavior

I have a table with 2 fields (foreign keys/relations) pointing to the same table. When I open the data section of this table, I want to see both fields (edit, etc).

Actual behavior

When I open the data section of the table to look at my data, I can see only one of the two fields.

Failure Logs

None.

Context

Example of table matching our context:

Model:

  • id
  • name
  • createdAt
  • updatedAt

Router:

  • id
  • prodModelId (belongsTo Model)
  • betaModelId (belongsTo Model)
  • slug
  • createdAt
  • updatedAt

When looking at “Router” data, I cannot see betaModelId, only prodModelId.

  • Package Version - forest-admin-sequelize: ^7.11.2
  • Express Version: ^4.17.1
  • Sequelize Version: ~5.15.1
  • Database Dialect: PostgreSQL
  • Project Name: Inarix

Thanks in advance!

Guillaume

Hello @Guillaume_Robin,

Thanks for your feedback and welcome in the community!

Can you share with us:

  • The code from you model declarations
  • The SQL declaration of your tables, with the relevant fields
  • The content of the file .forestadmin-schema.json regarding these models

Hi @GuillaumeGautreau,

Thanks you for you fast answer. Here is what you asked:

model_template (which is the one with two relations on the same table: model_instance)

module.exports = (sequelizeClient, DataTypes) => {
  const { Sequelize } = sequelizeClient;
  const modelTemplate = sequelizeClient.define(
    'model_template',
    {
      familyId: {
        type: DataTypes.INTEGER,
        allowNull: false,
      },
      slug: {
        type: DataTypes.STRING(64),
        unique: true,
        allowNull: false,
      },
      name: {
        type: DataTypes.STRING,
        allowNull: false,
      },
      description: {
        type: DataTypes.TEXT,
        allowNull: true,
      },
      prodModelInstanceId: {
        type: DataTypes.INTEGER,
        allowNull: true,
        defaultValue: null,
        as: 'Prod Model',
      },
      betaModelInstanceId: {
        type: DataTypes.INTEGER,
        allowNull: true,
        defaultValue: null,
        as: 'Beta Model',
      },
    },
    {
      tableName: 'model_template',
      schema: database_1.Schemas.IModels,
      hooks: {
        beforeCount(options) {
          options.raw = true;
        },
      },
    }
  );
  // eslint-disable-next-line no-unused-vars
  modelTemplate.associate = function(models) {
    // Define associations here
    // See http://docs.sequelizejs.com/en/latest/docs/associations/
    models.model_template.belongsTo(models.model_template_family, {
      foreignKey: 'familyId',
    });
    models.model_template.belongsTo(models.model_instance, {
      foreignKey: 'prodModelInstanceId',
      constraints: false,
    });
    models.model_template.belongsTo(models.model_instance, {
      foreignKey: 'betaModelInstanceId',
      constraints: false,
    });
    models.model_template.hasMany(
      models.prediction_post_processing_compatibility,
      { foreignKey: 'modelTemplateId', as: 'compats' }
    );
  };
  return modelTemplate;
};

model_instance

const uniqueComposite = 'modelInstanceVersionTemplateId';
module.exports = (sequelizeClient, DataTypes) => {
  const { Sequelize } = sequelizeClient;
  const modelInstance = sequelizeClient.define(
    'model_instance',
    {
      templateId: {
        type: DataTypes.INTEGER,
        allowNull: false,
        unique: uniqueComposite,
      },
      version: {
        type: DataTypes.STRING(32),
        allowNull: false,
        unique: uniqueComposite,
      },
      branchSlug: {
        type: DataTypes.STRING(128),
        allowNull: false,
      },
      dockerImageUri: {
        type: DataTypes.STRING,
        allowNull: true,
      },
      metadata: {
        type: DataTypes.JSON,
        allowNull: true,
        defaultValue: null,
      },
    },
    {
      tableName: 'model_instance',
      schema: database_1.Schemas.IModels,
      hooks: {
        beforeCount(options) {
          options.raw = true;
        },
      },
    }
  );
  // eslint-disable-next-line no-unused-vars
  modelInstance.associate = function(models) {
    // Define associations here
    // See http://docs.sequelizejs.com/en/latest/docs/associations/
    models.model_instance.belongsTo(models.model_template, {
      foreignKey: 'templateId',
    });
  };
  return modelInstance;
};

SQL

CREATE TABLE imodels.model_template
(
    id integer NOT NULL DEFAULT nextval('imodels.model_template_id_seq'::regclass),
    "familyId" integer NOT NULL,
    slug character varying(64) COLLATE pg_catalog."default" NOT NULL,
    name character varying(255) COLLATE pg_catalog."default" NOT NULL,
    description text COLLATE pg_catalog."default",
    "prodModelInstanceId" integer,
    "betaModelInstanceId" integer,
    "createdAt" timestamp with time zone NOT NULL,
    "updatedAt" timestamp with time zone NOT NULL,
    CONSTRAINT model_template_pkey PRIMARY KEY (id),
    CONSTRAINT model_template_slug_key UNIQUE (slug)
,
    CONSTRAINT "model_template_familyId_fkey" FOREIGN KEY ("familyId")
        REFERENCES imodels.model_template_family (id) MATCH SIMPLE
        ON UPDATE CASCADE
        ON DELETE CASCADE
)

CREATE TABLE imodels.model_instance
(
    id integer NOT NULL DEFAULT nextval('imodels.model_instance_id_seq'::regclass),
    "templateId" integer NOT NULL,
    version character varying(32) COLLATE pg_catalog."default" NOT NULL,
    "branchSlug" character varying(128) COLLATE pg_catalog."default" NOT NULL,
    metadata json,
    "createdAt" timestamp with time zone NOT NULL,
    "updatedAt" timestamp with time zone NOT NULL,
    "dockerImageUri" character varying(255) COLLATE pg_catalog."default" DEFAULT NULL::character varying,
    CONSTRAINT model_instance_pkey PRIMARY KEY (id),
    CONSTRAINT unique_imodels_model_instance_version_templateid UNIQUE (version, "templateId")
,
    CONSTRAINT "model_instance_templateId_fkey" FOREIGN KEY ("templateId")
        REFERENCES imodels.model_template (id) MATCH SIMPLE
        ON UPDATE CASCADE
        ON DELETE NO ACTION
)

.forestadmin-schema.json

{
    "name": "model_instance",
    "nameOld": "model_instance",
    "icon": null,
    "integration": null,
    "isReadOnly": false,
    "isSearchable": true,
    "isVirtual": false,
    "onlyForRelationships": false,
    "paginationType": "page",
    "fields": [{
      "field": "branchSlug",
      "type": "String",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": true,
      "isSortable": true,
      "isVirtual": false,
      "reference": null,
      "inverseOf": null,
      "validations": [{
        "message": null,
        "type": "is present",
        "value": null
      }]
    }, {
      "field": "createdAt",
      "type": "Date",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": false,
      "isSortable": true,
      "isVirtual": false,
      "reference": null,
      "inverseOf": null,
      "validations": []
    }, {
      "field": "dockerImageUri",
      "type": "String",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": false,
      "isSortable": true,
      "isVirtual": false,
      "reference": null,
      "inverseOf": null,
      "validations": []
    }, {
      "field": "id",
      "type": "Number",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": true,
      "isReadOnly": false,
      "isRequired": false,
      "isSortable": true,
      "isVirtual": false,
      "reference": null,
      "inverseOf": null,
      "validations": []
    }, {
      "field": "metadata",
      "type": "Json",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": false,
      "isSortable": true,
      "isVirtual": false,
      "reference": null,
      "inverseOf": null,
      "validations": []
    }, {
      "field": "model_template",
      "type": "Number",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": false,
      "isSortable": true,
      "isVirtual": false,
      "reference": "model_template.templateId",
      "inverseOf": null,
      "relationship": "BelongsTo",
      "validations": []
    }, {
      "field": "updatedAt",
      "type": "Date",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": false,
      "isSortable": true,
      "isVirtual": false,
      "reference": null,
      "inverseOf": null,
      "validations": []
    }, {
      "field": "version",
      "type": "String",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": true,
      "isSortable": true,
      "isVirtual": false,
      "reference": null,
      "inverseOf": null,
      "validations": [{
        "message": null,
        "type": "is present",
        "value": null
      }]
    }],
    "segments": [],
    "actions": []
  }, {
    "name": "model_template",
    "nameOld": "model_template",
    "icon": null,
    "integration": null,
    "isReadOnly": false,
    "isSearchable": true,
    "isVirtual": false,
    "onlyForRelationships": false,
    "paginationType": "page",
    "fields": [{
      "field": "compats",
      "type": ["Number"],
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": false,
      "isSortable": true,
      "isVirtual": false,
      "reference": "prediction_post_processing_compatibility.modelTemplateId",
      "inverseOf": null,
      "relationship": "HasMany",
      "validations": []
    }, {
      "field": "createdAt",
      "type": "Date",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": false,
      "isSortable": true,
      "isVirtual": false,
      "reference": null,
      "inverseOf": null,
      "validations": []
    }, {
      "field": "description",
      "type": "String",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": false,
      "isSortable": true,
      "isVirtual": false,
      "reference": null,
      "inverseOf": null,
      "validations": []
    }, {
      "field": "id",
      "type": "Number",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": true,
      "isReadOnly": false,
      "isRequired": false,
      "isSortable": true,
      "isVirtual": false,
      "reference": null,
      "inverseOf": null,
      "validations": []
    }, {
      "field": "model_instance",
      "type": "Number",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": false,
      "isSortable": true,
      "isVirtual": false,
      "reference": "model_instance.betaModelInstanceId",
      "inverseOf": null,
      "relationship": "BelongsTo",
      "validations": []
    }, {
      "field": "model_template_family",
      "type": "Number",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": false,
      "isSortable": true,
      "isVirtual": false,
      "reference": "model_template_family.familyId",
      "inverseOf": null,
      "relationship": "BelongsTo",
      "validations": []
    }, {
      "field": "name",
      "type": "String",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": true,
      "isSortable": true,
      "isVirtual": false,
      "reference": null,
      "inverseOf": null,
      "validations": [{
        "message": null,
        "type": "is present",
        "value": null
      }]
    }, {
      "field": "prodModelInstanceId",
      "type": "Number",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": false,
      "isSortable": true,
      "isVirtual": false,
      "reference": null,
      "inverseOf": null,
      "validations": []
    }, {
      "field": "slug",
      "type": "String",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": true,
      "isSortable": true,
      "isVirtual": false,
      "reference": null,
      "inverseOf": null,
      "validations": [{
        "message": null,
        "type": "is present",
        "value": null
      }]
    }, {
      "field": "updatedAt",
      "type": "Date",
      "defaultValue": null,
      "enums": null,
      "integration": null,
      "isFilterable": true,
      "isPrimaryKey": false,
      "isReadOnly": false,
      "isRequired": false,
      "isSortable": true,
      "isVirtual": false,
      "reference": null,
      "inverseOf": null,
      "validations": []
    }],
    "segments": [],
    "actions": []
  }

Best regards,

Guillaume

Hello again @Guillaume_Robin,

Can you try the following modifications on your model?

  1. Remove the declaration of fields used as foreign keys in your model (prodModelInstanceId and betaModelInstanceId)
  2. Add a parameter as in both belongsTo declarations, with a different name, such as models.model_template.belongsTo(models.model_instance, { foreignKey: 'betaModelInstanceId', constraints: false, as: "betaModelInstance" });

Then restart your server. Both relationship should be declared the same way in the file forestadmin-schema.json

1 Like

Hello @GuillaumeGautreau,

Thank you! I’ve been trying to understand how to fix this issue, and I never thought it could be this.

Best regards,

Guillaume

1 Like