diff --git a/example/main.go b/example/main.go index 7b839ee..039bd79 100644 --- a/example/main.go +++ b/example/main.go @@ -29,7 +29,7 @@ type TestModel3 struct { type TestModel4 struct { ID uint `gorm:"primarykey"` - Title string + Title *string } func main() { @@ -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, }) } } diff --git a/internal/adminpanel/app.go b/internal/adminpanel/app.go index eb81260..82f1092 100644 --- a/internal/adminpanel/app.go +++ b/internal/adminpanel/app.go @@ -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 @@ -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 != "" { @@ -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 } @@ -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 } @@ -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 } @@ -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) } @@ -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, diff --git a/internal/adminpanel/fields.go b/internal/adminpanel/fields.go index 8f5c5c5..4b7043c 100644 --- a/internal/adminpanel/fields.go +++ b/internal/adminpanel/fields.go @@ -10,6 +10,7 @@ type FieldConfig struct { Name string DisplayName string FieldType reflect.Type + IsPointer bool IncludeInListFetch bool IncludeInListDisplay bool IncludeInSearch bool diff --git a/internal/adminpanel/instance.go b/internal/adminpanel/instance.go index ab88334..9e1b464 100644 --- a/internal/adminpanel/instance.go +++ b/internal/adminpanel/instance.go @@ -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) + } } } @@ -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) + } } } @@ -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)