Skip to content

Commit

Permalink
Merge pull request #394 from codeconsole/6.0.x-customizable-blacklist
Browse files Browse the repository at this point in the history
Allow setting the default blacklist via properties
  • Loading branch information
codeconsole authored Jan 14, 2025
2 parents 9401cdd + 9061d84 commit 8ff774d
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 9 deletions.
23 changes: 18 additions & 5 deletions grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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) {
Expand All @@ -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 }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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']))
}

/**
Expand Down
7 changes: 7 additions & 0 deletions src/test/groovy/grails/plugin/formfields/mock/Person.groovy
Original file line number Diff line number Diff line change
@@ -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 {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ import grails.plugin.formfields.mock.*
abstract class AbstractFormFieldsTagLibSpec extends Specification implements GrailsWebUnitTest, DataTest {

Person personInstance
Cyborg cyborgInstance
Product productInstance

def setup() {
personInstance = new Person(name: "Bart Simpson", password: "bartman", gender: Gender.Male, dateOfBirth: new Date(87, 3, 19), minor: true)
personInstance.address = new Address(street: "94 Evergreen Terrace", city: "Springfield", country: "USA")
personInstance.emails = [home: "[email protected]", school: "[email protected]"]
productInstance = new Product(netPrice: 12.33, name: "<script>alert('XSS');</script>")
cyborgInstance = new Cyborg(name: "Hal", password: "monolith", gender: null)
}

def cleanup() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -13,6 +13,7 @@ class AllTagSpec extends AbstractFormFieldsTagLibSpec implements TagLibUnitTest<

def setupSpec() {
mockDomain(Person)
mockDomain(Cyborg)
}

def setup() {
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down

0 comments on commit 8ff774d

Please sign in to comment.