diff --git a/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy b/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy index aeb8cf8d..7f3135aa 100644 --- a/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy +++ b/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy @@ -65,6 +65,17 @@ class FormFieldsTagLib { @Value('${grails.plugin.fields.localizeNumbers:true}') Boolean localizeNumbers + @Value('${grails.plugin.fields.exclusions.list:#{T(java.util.Arrays).asList("id", "dateCreated", "lastUpdated")}}') + List<String> exclusionsList + @Value('${grails.plugin.fields.exclusions.input:#{T(java.util.Arrays).asList("version", "dateCreated", "lastUpdated")}}') + List<String> exclusionsInput + @Value('${grails.plugin.fields.exclusions.display:#{T(java.util.Arrays).asList("version", "dateCreated", "lastUpdated")}}') + List<String> exclusionsDisplay + + enum ExclusionType { + List, Display, Input + } + FormFieldsTemplateService formFieldsTemplateService BeanPropertyAccessorFactory beanPropertyAccessorFactory DomainPropertyFactory fieldsDomainPropertyFactory @@ -355,7 +366,7 @@ class FormFieldsTagLib { if (domainClass) { String template = attrs.remove('template') ?: 'list' - List properties = resolvePersistentProperties(domainClass, attrs) + List properties = resolvePersistentProperties(domainClass, attrs, ExclusionType.Display) out << render(template: "/templates/_fields/$template", model: attrs + [domainClass: domainClass, domainProperties: properties]) { prop -> BeanPropertyAccessor propertyAccessor = resolveProperty(bean, prop.name) Map model = buildModel(propertyAccessor, attrs, 'HTML') @@ -448,7 +459,7 @@ class FormFieldsTagLib { } else if (attrs.containsKey('properties')) { return getList(attrs.remove('properties')) } else { - List<String> properties = resolvePersistentProperties(domainClass, attrs, true)*.name + List<String> properties = resolvePersistentProperties(domainClass, attrs, ExclusionType.List)*.name int maxProperties = attrs.containsKey('maxProperties') ? attrs.remove('maxProperties').toInteger() : 7 if (maxProperties && properties.size() > maxProperties) { properties = properties[0..<maxProperties] @@ -578,9 +589,10 @@ class FormFieldsTagLib { } } - private List<PersistentProperty> resolvePersistentProperties(PersistentEntity domainClass, Map attrs, boolean list = false) { + private List<PersistentProperty> resolvePersistentProperties(PersistentEntity domainClass, Map attrs, ExclusionType exclusionType = ExclusionType.Input) { List<PersistentProperty> properties + boolean list = exclusionType == ExclusionType.List if (attrs.order) { def orderBy = getList(attrs.order) if (attrs.except) { @@ -590,9 +602,10 @@ class FormFieldsTagLib { fieldsDomainPropertyFactory.build(domainClass.getPropertyByName(propertyName)) } } else { - properties = list ? domainModelService.getListOutputProperties(domainClass) : domainModelService.getInputProperties(domainClass) + properties = list ? domainModelService.getListOutputProperties(domainClass) : domainModelService.getInputProperties(domainClass, + exclusionType == ExclusionType.Input? exclusionsInput : exclusionsDisplay) // If 'except' is not set, but 'list' is, exclude 'id', 'dateCreated' and 'lastUpdated' by default - List<String> blacklist = attrs.containsKey('except') ? getList(attrs.except) : (list ? ['id', 'dateCreated', 'lastUpdated'] : []) + List<String> blacklist = attrs.containsKey('except') ? getList(attrs.except) : (list ? exclusionsList : []) properties.removeAll { it.name in blacklist } } diff --git a/src/main/groovy/org/grails/scaffolding/model/DomainModelService.groovy b/src/main/groovy/org/grails/scaffolding/model/DomainModelService.groovy index 079ba48f..6d592bba 100644 --- a/src/main/groovy/org/grails/scaffolding/model/DomainModelService.groovy +++ b/src/main/groovy/org/grails/scaffolding/model/DomainModelService.groovy @@ -16,6 +16,7 @@ interface DomainModelService { * @param domainClass The persistent entity */ List<DomainProperty> getInputProperties(PersistentEntity domainClass) + List<DomainProperty> getInputProperties(PersistentEntity domainClass, List blackList) /** * The list of {@link DomainProperty} instances that are to be visible diff --git a/src/main/groovy/org/grails/scaffolding/model/DomainModelServiceImpl.groovy b/src/main/groovy/org/grails/scaffolding/model/DomainModelServiceImpl.groovy index 4feb6438..df5c4eca 100644 --- a/src/main/groovy/org/grails/scaffolding/model/DomainModelServiceImpl.groovy +++ b/src/main/groovy/org/grails/scaffolding/model/DomainModelServiceImpl.groovy @@ -91,8 +91,8 @@ class DomainModelServiceImpl implements DomainModelService { * @see {@link DomainModelServiceImpl#getProperties} * @param domainClass The persistent entity */ - List<DomainProperty> getInputProperties(PersistentEntity domainClass) { - getProperties(domainClass, ['version', 'dateCreated', 'lastUpdated']) + List<DomainProperty> getInputProperties(PersistentEntity domainClass, List<String> blackList = null) { + getProperties(domainClass, new ArrayList<>(blackList ?: ['version', 'dateCreated', 'lastUpdated'])) } /** diff --git a/src/test/groovy/grails/plugin/formfields/mock/Person.groovy b/src/test/groovy/grails/plugin/formfields/mock/Person.groovy index e7b40b70..57359dcb 100644 --- a/src/test/groovy/grails/plugin/formfields/mock/Person.groovy +++ b/src/test/groovy/grails/plugin/formfields/mock/Person.groovy @@ -1,7 +1,14 @@ package grails.plugin.formfields.mock +import grails.gorm.annotation.AutoTimestamp import grails.persistence.Entity +@Entity +class Cyborg extends HomoSapiens { + @AutoTimestamp(AutoTimestamp.EventType.CREATED) Date created + @AutoTimestamp Date modified +} + @Entity class Person extends HomoSapiens {} diff --git a/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy b/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy index f9a1716f..874c33f0 100644 --- a/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy +++ b/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy @@ -18,6 +18,7 @@ import grails.plugin.formfields.mock.* abstract class AbstractFormFieldsTagLibSpec extends Specification implements GrailsWebUnitTest, DataTest { Person personInstance + Cyborg cyborgInstance Product productInstance def setup() { @@ -25,6 +26,7 @@ abstract class AbstractFormFieldsTagLibSpec extends Specification implements Gra personInstance.address = new Address(street: "94 Evergreen Terrace", city: "Springfield", country: "USA") personInstance.emails = [home: "bart@thesimpsons.net", school: "bart.simpson@springfieldelementary.edu"] productInstance = new Product(netPrice: 12.33, name: "<script>alert('XSS');</script>") + cyborgInstance = new Cyborg(name: "Hal", password: "monolith", gender: null) } def cleanup() { diff --git a/src/test/groovy/grails/plugin/formfields/taglib/AllTagSpec.groovy b/src/test/groovy/grails/plugin/formfields/taglib/AllTagSpec.groovy index bed7a921..4b6f63e0 100644 --- a/src/test/groovy/grails/plugin/formfields/taglib/AllTagSpec.groovy +++ b/src/test/groovy/grails/plugin/formfields/taglib/AllTagSpec.groovy @@ -1,9 +1,9 @@ package grails.plugin.formfields.taglib +import grails.plugin.formfields.mock.Cyborg import grails.plugin.formfields.mock.Person import grails.plugin.formfields.* import grails.testing.web.taglib.TagLibUnitTest -import org.grails.taglib.GrailsTagException import spock.lang.* @Unroll @@ -13,6 +13,7 @@ class AllTagSpec extends AbstractFormFieldsTagLibSpec implements TagLibUnitTest< def setupSpec() { mockDomain(Person) + mockDomain(Cyborg) } def setup() { @@ -58,6 +59,24 @@ class AllTagSpec extends AbstractFormFieldsTagLibSpec implements TagLibUnitTest< included << ['salutation', 'name', 'password', 'gender', 'dateOfBirth', 'address.street'] } + void 'all tag skips custom #excluded property and includes #included property'() { + given: + views["/_fields/default/_field.gsp"] = '${property} ' + views["/_fields/default/_wrapper.gsp"] = '${widget}' + tagLib.exclusionsInput = ['id', 'created', 'modified', 'version'] + + when: + def output = applyTemplate('<f:all bean="cyborgInstance"/>', [cyborgInstance: cyborgInstance]) + + then: + !output.contains(excluded) + output.contains(included) + + where: + excluded << ['id', 'created', 'modified', 'version', 'onLoad', 'excludedProperty', 'displayFalseProperty'] + included << ['salutation', 'name', 'password', 'gender', 'dateOfBirth', 'address.street', 'minor'] + } + @Issue('https://github.com/grails-fields-plugin/grails-fields/issues/12') void 'all tag skips properties listed with the except attribute'() { given: diff --git a/src/test/groovy/org/grails/scaffolding/model/DomainModelServiceSpec.groovy b/src/test/groovy/org/grails/scaffolding/model/DomainModelServiceSpec.groovy index 7bbb0c1e..d225495f 100644 --- a/src/test/groovy/org/grails/scaffolding/model/DomainModelServiceSpec.groovy +++ b/src/test/groovy/org/grails/scaffolding/model/DomainModelServiceSpec.groovy @@ -90,7 +90,7 @@ class DomainModelServiceSpec extends Specification implements MocksDomain { 1 * getName() >> "lastUpdated" } DomainProperty version = Mock(DomainProperty) { - 1 * getName() >> "lastUpdated" + 1 * getName() >> "version" } domainModelService.domainPropertyFactory = Mock(DomainPropertyFactoryImpl) { 1 * build(persistentProperty1) >> dateCreated @@ -106,6 +106,34 @@ class DomainModelServiceSpec extends Specification implements MocksDomain { properties.empty } + void "test getEditableProperties excluded by overriding default exclusions"() { + given: + PersistentProperty persistentProperty1 = Mock(PersistentProperty) + PersistentProperty persistentProperty2 = Mock(PersistentProperty) + PersistentProperty persistentProperty3 = Mock(PersistentProperty) + DomainProperty created = Mock(DomainProperty) { + 1 * getName() >> "created" + } + DomainProperty modified = Mock(DomainProperty) { + 1 * getName() >> "modified" + } + DomainProperty version = Mock(DomainProperty) { + 1 * getName() >> "version" + } + domainModelService.domainPropertyFactory = Mock(DomainPropertyFactoryImpl) { + 1 * build(persistentProperty1) >> created + 1 * build(persistentProperty2) >> modified + 1 * build(persistentProperty3) >> version + } + 1 * domainClass.getPersistentProperties() >> [persistentProperty1, persistentProperty2, persistentProperty3] + + when: + List<DomainProperty> properties = domainModelService.getInputProperties(domainClass, ['created', 'modified', 'version']).toList() + + then: "properties that are excluded by overriding default exclusion are excluded" + properties.empty + } + void "test getEditableProperties constraints display false"() { given: PersistentProperty bar = Mock()