mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 07:03:38 +00:00
Merge pull request #12183 from strapi/content-15/fix-non-localized-media-not-populated-in-new-locale
Add all non-localized fields (media, compo, dz) in /get-non-localized-fields response
This commit is contained in:
commit
21bf31b5a3
@ -6,6 +6,7 @@ const {
|
||||
getPrivateAttributes,
|
||||
getVisibleAttributes,
|
||||
getNonWritableAttributes,
|
||||
getScalarAttributes,
|
||||
constants,
|
||||
} = require('../content-types');
|
||||
|
||||
@ -196,4 +197,57 @@ describe('Content types utils', () => {
|
||||
expect(isTypedAttribute({ type: 'test' }, 'other-type')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getScalarAttributes', () => {
|
||||
test('returns only scalar attributes', () => {
|
||||
const schema = {
|
||||
attributes: {
|
||||
mediaField: { type: 'media' },
|
||||
componentField: { type: 'component' },
|
||||
relationField: { type: 'relation' },
|
||||
dynamiczoneField: { type: 'dynamiczone' },
|
||||
stringField: { type: 'string' },
|
||||
textField: { type: 'text' },
|
||||
richtextField: { type: 'richtext' },
|
||||
enumerationField: { type: 'enumeration' },
|
||||
emailField: { type: 'email' },
|
||||
passwordField: { type: 'password' },
|
||||
uidField: { type: 'uid' },
|
||||
dateField: { type: 'date' },
|
||||
timeField: { type: 'time' },
|
||||
datetimeField: { type: 'datetime' },
|
||||
timestampField: { type: 'timestamp' },
|
||||
integerField: { type: 'integer' },
|
||||
bigintegerField: { type: 'biginteger' },
|
||||
floatField: { type: 'float' },
|
||||
decimalField: { type: 'decimal' },
|
||||
booleanField: { type: 'boolean' },
|
||||
arrayField: { type: 'array' },
|
||||
jsonField: { type: 'json' },
|
||||
},
|
||||
};
|
||||
|
||||
const scalarAttributes = getScalarAttributes(schema);
|
||||
expect(scalarAttributes).toEqual([
|
||||
'stringField',
|
||||
'textField',
|
||||
'richtextField',
|
||||
'enumerationField',
|
||||
'emailField',
|
||||
'passwordField',
|
||||
'uidField',
|
||||
'dateField',
|
||||
'timeField',
|
||||
'datetimeField',
|
||||
'timestampField',
|
||||
'integerField',
|
||||
'bigintegerField',
|
||||
'floatField',
|
||||
'decimalField',
|
||||
'booleanField',
|
||||
'arrayField',
|
||||
'jsonField',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -104,13 +104,21 @@ const isPrivateAttribute = (model = {}, attributeName) => {
|
||||
};
|
||||
|
||||
const isScalarAttribute = attribute => {
|
||||
return !['component', 'relation', 'dynamiczone'].includes(attribute.type);
|
||||
return !['media', 'component', 'relation', 'dynamiczone'].includes(attribute.type);
|
||||
};
|
||||
|
||||
const isMediaAttribute = attr => {
|
||||
return attr.type === 'media';
|
||||
const getScalarAttributes = schema => {
|
||||
return _.reduce(
|
||||
schema.attributes,
|
||||
(acc, attr, attrName) => {
|
||||
if (isScalarAttribute(attr)) acc.push(attrName);
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
const isMediaAttribute = attribute => attribute.type === 'media';
|
||||
const isRelationalAttribute = attribute => attribute.type === 'relation';
|
||||
const isComponentAttribute = attribute => ['component', 'dynamiczone'].includes(attribute.type);
|
||||
|
||||
@ -144,6 +152,7 @@ module.exports = {
|
||||
isPrivateAttribute,
|
||||
constants,
|
||||
getNonWritableAttributes,
|
||||
getScalarAttributes,
|
||||
getWritableAttributes,
|
||||
isWritableAttribute,
|
||||
getNonVisibleAttributes,
|
||||
|
||||
@ -7,9 +7,9 @@ const ctService = require('../../services/content-types')();
|
||||
describe('i18n - Controller - content-types', () => {
|
||||
describe('getNonLocalizedAttributes', () => {
|
||||
beforeEach(() => {
|
||||
const getModel = () => {};
|
||||
const contentType = () => ({});
|
||||
global.strapi = {
|
||||
getModel,
|
||||
contentType,
|
||||
plugins: { i18n: { services: { 'content-types': ctService } } },
|
||||
admin: { services: { constants: { READ_ACTION: 'read', CREATE_ACTION: 'create' } } },
|
||||
};
|
||||
@ -42,10 +42,10 @@ describe('i18n - Controller - content-types', () => {
|
||||
test('entity not found', async () => {
|
||||
const notFound = jest.fn();
|
||||
const findOne = jest.fn(() => Promise.resolve(undefined));
|
||||
const getModel = jest.fn(() => ({ pluginOptions: { i18n: { localized: true } } }));
|
||||
const contentType = jest.fn(() => ({ pluginOptions: { i18n: { localized: true } } }));
|
||||
|
||||
global.strapi.query = () => ({ findOne });
|
||||
global.strapi.getModel = getModel;
|
||||
global.strapi.contentType = contentType;
|
||||
const ctx = {
|
||||
state: { user: {} },
|
||||
request: {
|
||||
@ -87,10 +87,10 @@ describe('i18n - Controller - content-types', () => {
|
||||
|
||||
const findOne = jest.fn(() => Promise.resolve(entity));
|
||||
const findMany = jest.fn(() => Promise.resolve(permissions));
|
||||
const getModel = jest.fn(() => model);
|
||||
const contentType = jest.fn(() => model);
|
||||
|
||||
global.strapi.query = () => ({ findOne });
|
||||
global.strapi.getModel = getModel;
|
||||
global.strapi.contentType = contentType;
|
||||
global.strapi.admin.services.permission = { findMany };
|
||||
const ctx = {
|
||||
state: { user: { roles: [{ id: 1 }, { id: 2 }] } },
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const { pick, uniq, prop, getOr, flatten, pipe, map } = require('lodash/fp');
|
||||
const { pick, uniq, prop, getOr, flatten, pipe, map, difference } = require('lodash/fp');
|
||||
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
|
||||
const { ApplicationError } = require('@strapi/utils').errors;
|
||||
const { getService } = require('../utils');
|
||||
const { validateGetNonLocalizedAttributesInput } = require('../validation/content-types');
|
||||
|
||||
const { getScalarAttributes } = contentTypesUtils;
|
||||
const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants;
|
||||
|
||||
const getLocalesProperty = getOr([], 'properties.locales');
|
||||
@ -20,10 +21,18 @@ module.exports = {
|
||||
|
||||
await validateGetNonLocalizedAttributesInput({ model, id, locale });
|
||||
|
||||
const modelDef = strapi.getModel(model);
|
||||
const { copyNonLocalizedAttributes, isLocalizedContentType } = getService('content-types');
|
||||
const {
|
||||
copyNonLocalizedAttributes,
|
||||
isLocalizedContentType,
|
||||
getNonLocalizedAttributes,
|
||||
} = getService('content-types');
|
||||
const { READ_ACTION, CREATE_ACTION } = strapi.admin.services.constants;
|
||||
|
||||
const modelDef = strapi.contentType(model);
|
||||
const scalarAttributes = getScalarAttributes(modelDef);
|
||||
const nonLocalizedAttributes = getNonLocalizedAttributes(modelDef);
|
||||
const attributesToPopulate = difference(nonLocalizedAttributes, scalarAttributes);
|
||||
|
||||
if (!isLocalizedContentType(modelDef)) {
|
||||
throw new ApplicationError('model.not.localized');
|
||||
}
|
||||
@ -32,7 +41,7 @@ module.exports = {
|
||||
|
||||
const entity = await strapi
|
||||
.query(model)
|
||||
.findOne({ where: params, populate: ['localizations'] });
|
||||
.findOne({ where: params, populate: [...attributesToPopulate, 'localizations'] });
|
||||
|
||||
if (!entity) {
|
||||
return ctx.notFound();
|
||||
|
||||
@ -5,7 +5,6 @@ const { pick, pipe, has, prop, isNil, cloneDeep, isArray } = require('lodash/fp'
|
||||
const {
|
||||
isRelationalAttribute,
|
||||
getVisibleAttributes,
|
||||
isMediaAttribute,
|
||||
isTypedAttribute,
|
||||
} = require('@strapi/utils').contentTypes;
|
||||
const { ApplicationError } = require('@strapi/utils').errors;
|
||||
@ -82,12 +81,10 @@ const getAndValidateRelatedEntity = async (relatedEntityId, model, locale) => {
|
||||
* @param {*} attribute
|
||||
* @returns
|
||||
*/
|
||||
const isLocalizedAttribute = (model, attributeName) => {
|
||||
const attribute = model.attributes[attributeName];
|
||||
|
||||
const isLocalizedAttribute = attribute => {
|
||||
return (
|
||||
hasLocalizedOption(attribute) ||
|
||||
(isRelationalAttribute(attribute) && !isMediaAttribute(attribute)) ||
|
||||
isRelationalAttribute(attribute) ||
|
||||
isTypedAttribute(attribute, 'uid')
|
||||
);
|
||||
};
|
||||
@ -108,7 +105,7 @@ const isLocalizedContentType = model => {
|
||||
*/
|
||||
const getNonLocalizedAttributes = model => {
|
||||
return getVisibleAttributes(model).filter(
|
||||
attributeName => !isLocalizedAttribute(model, attributeName)
|
||||
attrName => !isLocalizedAttribute(model.attributes[attrName])
|
||||
);
|
||||
};
|
||||
|
||||
@ -170,8 +167,8 @@ const copyNonLocalizedAttributes = (model, entry) => {
|
||||
* @returns {string[]}
|
||||
*/
|
||||
const getLocalizedAttributes = model => {
|
||||
return getVisibleAttributes(model).filter(attributeName =>
|
||||
isLocalizedAttribute(model, attributeName)
|
||||
return getVisibleAttributes(model).filter(attrName =>
|
||||
isLocalizedAttribute(model.attributes[attrName])
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ const validateGetNonLocalizedAttributesSchema = yup
|
||||
.shape({
|
||||
model: yup.string().required(),
|
||||
id: yup.mixed().when('model', {
|
||||
is: model => get('kind', strapi.getModel(model)) === 'singleType',
|
||||
is: model => get('kind', strapi.contentType(model)) === 'singleType',
|
||||
then: yup.strapiID().nullable(),
|
||||
otherwise: yup.strapiID().required(),
|
||||
}),
|
||||
|
||||
@ -0,0 +1,173 @@
|
||||
'use strict';
|
||||
|
||||
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
|
||||
const { createAuthRequest } = require('../../../../../test/helpers/request');
|
||||
const { createTestBuilder } = require('../../../../../test/helpers/builder');
|
||||
|
||||
let strapi;
|
||||
let rq;
|
||||
|
||||
const compoModel = {
|
||||
collectionName: 'components_default_simples',
|
||||
displayName: 'simple',
|
||||
description: '',
|
||||
icon: 'ambulance',
|
||||
attributes: {
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
test: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const categoryModel = {
|
||||
kind: 'collectionType',
|
||||
collectionName: 'categories',
|
||||
displayName: 'Category',
|
||||
singularName: 'category',
|
||||
pluralName: 'categories',
|
||||
description: '',
|
||||
name: 'Category',
|
||||
options: {
|
||||
draftAndPublish: false,
|
||||
},
|
||||
pluginOptions: {
|
||||
i18n: {
|
||||
localized: true,
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const dogSchema = {
|
||||
kind: 'collectionType',
|
||||
collectionName: 'dogs',
|
||||
displayName: 'Dog',
|
||||
singularName: 'dog',
|
||||
pluralName: 'dogs',
|
||||
options: {
|
||||
draftAndPublish: false,
|
||||
},
|
||||
pluginOptions: {
|
||||
i18n: {
|
||||
localized: true,
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
name: {
|
||||
type: 'string',
|
||||
pluginOptions: {
|
||||
i18n: {
|
||||
localized: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
},
|
||||
categories: {
|
||||
type: 'relation',
|
||||
relation: 'manyToMany',
|
||||
target: 'api::category.category',
|
||||
targetAttribute: 'dogs',
|
||||
},
|
||||
myCompo: {
|
||||
type: 'component',
|
||||
repeatable: false,
|
||||
component: 'default.simple',
|
||||
},
|
||||
myDz: {
|
||||
type: 'dynamiczone',
|
||||
components: ['default.simple'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const dogs = [
|
||||
{
|
||||
name: 'Pilou',
|
||||
description: 'A good girl',
|
||||
myCompo: { name: 'my compo' },
|
||||
myDz: [{ name: 'my compo', __component: 'default.simple' }],
|
||||
},
|
||||
];
|
||||
|
||||
const categories = [{ name: 'Labrador' }];
|
||||
|
||||
const data = {};
|
||||
|
||||
describe('i18n - Content API', () => {
|
||||
const builder = createTestBuilder();
|
||||
|
||||
beforeAll(async () => {
|
||||
await builder
|
||||
.addComponent(compoModel)
|
||||
.addContentTypes([categoryModel, dogSchema])
|
||||
.addFixtures('plugin::i18n.locale', [
|
||||
{
|
||||
name: 'French (fr)',
|
||||
code: 'fr',
|
||||
},
|
||||
])
|
||||
.addFixtures(dogSchema.singularName, dogs)
|
||||
.addFixtures(categoryModel.singularName, categories)
|
||||
.build();
|
||||
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createAuthRequest({ strapi });
|
||||
|
||||
data.dogs = await builder.sanitizedFixturesFor(dogSchema.singularName, strapi);
|
||||
data.categories = await builder.sanitizedFixturesFor(categoryModel.singularName, strapi);
|
||||
const { body } = await rq({
|
||||
method: 'PUT',
|
||||
url: `/content-manager/collection-types/api::dog.dog/${data.dogs[0].id}`,
|
||||
body: {
|
||||
categories: [data.categories[0].id],
|
||||
},
|
||||
});
|
||||
data.dogs[0] = body;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await strapi.destroy();
|
||||
await builder.cleanup();
|
||||
});
|
||||
|
||||
describe('Test content-types', () => {
|
||||
describe('getNonLocalizedAttributes', () => {
|
||||
test('Get non localized attributes (including compo and dz)', async () => {
|
||||
const res = await rq({
|
||||
method: 'POST',
|
||||
url: '/i18n/content-manager/actions/get-non-localized-fields',
|
||||
body: {
|
||||
id: data.dogs[0].id,
|
||||
locale: 'fr',
|
||||
model: 'api::dog.dog',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.body).toMatchObject({
|
||||
nonLocalizedFields: {
|
||||
description: 'A good girl',
|
||||
myCompo: { name: 'my compo', test: null },
|
||||
myDz: [
|
||||
{
|
||||
__component: 'default.simple',
|
||||
name: 'my compo',
|
||||
test: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
localizations: [{ id: 1, locale: 'en' }],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user