Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enhance pointer handling in models and forms #11

Merged
merged 1 commit into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type TestModel3 struct {

type TestModel4 struct {
ID uint `gorm:"primarykey"`
Title string
Title *string
}

func main() {
Expand Down Expand Up @@ -119,8 +119,9 @@ func populateTestModels(db *gorm.DB) {
}

for i := 0; i < 100; i++ {
title := fmt.Sprintf("Post Title %d", i)
db.Create(&TestModel4{
Title: fmt.Sprintf("Post Title %d", i),
Title: &title,
})
}
}
45 changes: 27 additions & 18 deletions internal/adminpanel/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ func (a *App) RegisterModel(model interface{}, orm ORMIntegrator) (*Model, error
for i := 0; i < modelType.NumField(); i++ {
field := modelType.Field(i)
fieldName := field.Name

fieldType := field.Type

isPointer := false
underlyingType := fieldType
if fieldType.Kind() == reflect.Ptr {
isPointer = true
underlyingType = fieldType.Elem()
}

fieldDisplayName := utils.HumanizeName(fieldName)
includeInList := true
includeInFetch := true
Expand Down Expand Up @@ -162,11 +172,9 @@ func (a *App) RegisterModel(model interface{}, orm ORMIntegrator) (*Model, error
}
}

fieldType := field.Type

var formField form.Field
if includeInAddForm || includeInEditForm {
switch fieldType.Kind() {
switch underlyingType.Kind() {
case reflect.String:
formField = &fields.TextField{}
if tag != "" {
Expand All @@ -190,21 +198,21 @@ func (a *App) RegisterModel(model interface{}, orm ORMIntegrator) (*Model, error
case "maxLength":
maxLengthInterface, err := utils.ConvertStringToType(value, reflect.TypeOf(uint(0)))
if err != nil {
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, fieldType.Name(), err)
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, underlyingType.Name(), err)
}
maxLength, ok := maxLengthInterface.(uint)
if !ok {
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, fieldType.Name(), err)
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, underlyingType.Name(), err)
}
formField.(*fields.TextField).MaxLength = &maxLength
case "minLength":
minLengthInterface, err := utils.ConvertStringToType(value, reflect.TypeOf(uint(0)))
if err != nil {
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, fieldType.Name(), err)
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, underlyingType.Name(), err)
}
minLength, ok := minLengthInterface.(uint)
if !ok {
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, fieldType.Name(), err)
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, underlyingType.Name(), err)
}
formField.(*fields.TextField).MinLength = &minLength
}
Expand All @@ -229,21 +237,21 @@ func (a *App) RegisterModel(model interface{}, orm ORMIntegrator) (*Model, error
case "max":
maxInterface, err := utils.ConvertStringToType(value, reflect.TypeOf(0))
if err != nil {
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, fieldType.Name(), err)
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, underlyingType.Name(), err)
}
maxValue, ok := maxInterface.(int)
if !ok {
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, fieldType.Name(), err)
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, underlyingType.Name(), err)
}
formField.(*fields.IntegerField).MaxValue = &maxValue
case "min":
minInterface, err := utils.ConvertStringToType(value, reflect.TypeOf(0))
if err != nil {
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, fieldType.Name(), err)
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, underlyingType.Name(), err)
}
minValue, ok := minInterface.(int)
if !ok {
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, fieldType.Name(), err)
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, underlyingType.Name(), err)
}
formField.(*fields.IntegerField).MinValue = &minValue
}
Expand All @@ -268,21 +276,21 @@ func (a *App) RegisterModel(model interface{}, orm ORMIntegrator) (*Model, error
case "max":
maxInterface, err := utils.ConvertStringToType(value, reflect.TypeOf(float64(0)))
if err != nil {
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, fieldType.Name(), err)
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, underlyingType.Name(), err)
}
maxValue, ok := maxInterface.(float64)
if !ok {
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, fieldType.Name(), err)
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, underlyingType.Name(), err)
}
formField.(*fields.FloatField).MaxValue = &maxValue
case "min":
minInterface, err := utils.ConvertStringToType(value, reflect.TypeOf(float64(0)))
if err != nil {
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, fieldType.Name(), err)
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, underlyingType.Name(), err)
}
minValue, ok := minInterface.(float64)
if !ok {
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, fieldType.Name(), err)
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, underlyingType.Name(), err)
}
formField.(*fields.FloatField).MinValue = &minValue
}
Expand Down Expand Up @@ -332,9 +340,9 @@ func (a *App) RegisterModel(model interface{}, orm ORMIntegrator) (*Model, error

switch key {
case "initial":
convertedValue, err := utils.ConvertStringToType(value, fieldType)
convertedValue, err := utils.ConvertStringToType(value, underlyingType)
if err != nil {
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, fieldType.Name(), err)
return nil, fmt.Errorf("error converting value '%s' to type '%s': %w", value, underlyingType.Name(), err)
}
formField.RegisterInitialValue(convertedValue)
}
Expand Down Expand Up @@ -364,7 +372,8 @@ func (a *App) RegisterModel(model interface{}, orm ORMIntegrator) (*Model, error
fieldConfigs = append(fieldConfigs, FieldConfig{
Name: fieldName,
DisplayName: fieldDisplayName,
FieldType: fieldType,
FieldType: underlyingType,
IsPointer: isPointer,
IncludeInListDisplay: includeInList,
IncludeInListFetch: includeInFetch,
IncludeInSearch: includeInSearch,
Expand Down
1 change: 1 addition & 0 deletions internal/adminpanel/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type FieldConfig struct {
Name string
DisplayName string
FieldType reflect.Type
IsPointer bool
IncludeInListFetch bool
IncludeInListDisplay bool
IncludeInSearch bool
Expand Down
89 changes: 70 additions & 19 deletions internal/adminpanel/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,21 +226,35 @@ func (f *ModelAddForm) Save(values map[string]form.HTMLType) (interface{}, error
return nil, fmt.Errorf("field %s is not settable", fieldName)
}

if value == nil {
if fieldVal.Kind() == reflect.Ptr {
fieldVal.Set(reflect.Zero(fieldVal.Type()))
}
continue
}

val := reflect.ValueOf(value)

if val.Type().AssignableTo(fieldVal.Type()) {
fieldVal.Set(val)
} else if val.Type().ConvertibleTo(fieldVal.Type()) {
fieldVal.Set(val.Convert(fieldVal.Type()))
if fieldVal.Kind() == reflect.Ptr {
if value == nil {
fieldVal.Set(reflect.Zero(fieldVal.Type()))
continue
}
elemType := fieldVal.Type().Elem()
newVal := reflect.New(elemType)
if val.Type().AssignableTo(elemType) {
newVal.Elem().Set(val)
} else if val.Type().ConvertibleTo(elemType) {
newVal.Elem().Set(val.Convert(elemType))
} else {
return nil, fmt.Errorf("field %s has invalid type", fieldName)
}
fieldVal.Set(newVal)
} else {
return nil, fmt.Errorf("field %s has invalid type", fieldName)
if value == nil {
fieldVal.Set(reflect.Zero(fieldVal.Type()))
continue
}
if val.Type().AssignableTo(fieldVal.Type()) {
fieldVal.Set(val)
} else if val.Type().ConvertibleTo(fieldVal.Type()) {
fieldVal.Set(val.Convert(fieldVal.Type()))
} else {
return nil, fmt.Errorf("field %s has invalid type", fieldName)
}
}
}

Expand Down Expand Up @@ -279,19 +293,44 @@ func (f *ModelEditForm) Save(values map[string]form.HTMLType) (interface{}, erro

for fieldName, value := range cleanValues {
fieldVal := instanceVal.FieldByName(fieldName)

if !fieldVal.IsValid() {
continue
return nil, fmt.Errorf("field %s not found in model", fieldName)
}

if !fieldVal.CanSet() {
return nil, fmt.Errorf("field %s is not settable", fieldName)
}

val := reflect.ValueOf(value)
if val.Type().AssignableTo(fieldVal.Type()) {
fieldVal.Set(val)
} else if val.Type().ConvertibleTo(fieldVal.Type()) {
fieldVal.Set(val.Convert(fieldVal.Type()))

if fieldVal.Kind() == reflect.Ptr {
if value == nil {
fieldVal.Set(reflect.Zero(fieldVal.Type()))
continue
}
elemType := fieldVal.Type().Elem()
newVal := reflect.New(elemType)
if val.Type().AssignableTo(elemType) {
newVal.Elem().Set(val)
} else if val.Type().ConvertibleTo(elemType) {
newVal.Elem().Set(val.Convert(elemType))
} else {
return nil, fmt.Errorf("field %s has invalid type", fieldName)
}
fieldVal.Set(newVal)
} else {
return nil, fmt.Errorf("field %s has invalid type", fieldName)
if value == nil {
fieldVal.Set(reflect.Zero(fieldVal.Type()))
continue
}
if val.Type().AssignableTo(fieldVal.Type()) {
fieldVal.Set(val)
} else if val.Type().ConvertibleTo(fieldVal.Type()) {
fieldVal.Set(val.Convert(fieldVal.Type()))
} else {
return nil, fmt.Errorf("field %s has invalid type", fieldName)
}
}
}

Expand Down Expand Up @@ -520,7 +559,19 @@ func (m *Model) GetEditHandler() HandlerFunc {
if field.EditFormField == nil {
continue
}
initialValuesMap[field.Name] = reflect.ValueOf(instanceData).Elem().FieldByName(field.Name).Interface()
fieldValue := reflect.ValueOf(instanceData).Elem().FieldByName(field.Name)
var value interface{}
if field.IsPointer {
if fieldValue.IsNil() {
value = nil
} else {
value = fieldValue.Elem().Interface()
}
} else {
value = fieldValue.Interface()
}

initialValuesMap[field.Name] = value
}

err = formInstance.RegisterInitialValues(initialValuesMap)
Expand Down