func (res *Resource) findOneHandler(result interface{}, metaValues *MetaValues, context *qor.Context) error { if res.HasPermission(roles.Read, context) { var ( scope = context.GetDB().NewScope(res.Value) primaryField = res.PrimaryField() primaryKey string ) if metaValues == nil { primaryKey = context.ResourceID } else if primaryField == nil { return nil } else if id := metaValues.Get(primaryField.Name); id != nil { primaryKey = utils.ToString(id.Value) } if primaryKey != "" { if metaValues != nil { if destroy := metaValues.Get("_destroy"); destroy != nil { if fmt.Sprint(destroy.Value) != "0" && res.HasPermission(roles.Delete, context) { context.GetDB().Delete(result, fmt.Sprintf("%v = ?", scope.Quote(primaryField.DBName)), primaryKey) return ErrProcessorSkipLeft } } } return context.GetDB().First(result, fmt.Sprintf("%v = ?", scope.Quote(primaryField.DBName)), primaryKey).Error } return errors.New("failed to find") } else { return roles.ErrPermissionDenied } }
func init() { Admin = admin.New(&qor.Config{DB: db.Publish.DraftDB()}) Admin.SetAuth(Auth{}) assetManager := Admin.AddResource(&admin.AssetManager{}, &admin.Config{Invisible: true}) product := Admin.AddResource(&models.Product{}, &admin.Config{Menu: []string{"Product Management"}}) product.Meta(&admin.Meta{Name: "MadeCountry", Type: "select_one", Collection: Countries}) product.Meta(&admin.Meta{Name: "Description", Type: "rich_editor", Resource: assetManager}) for _, country := range Countries { var country = country product.Scope(&admin.Scope{Name: country, Group: "Made Country", Handle: func(db *gorm.DB, ctx *qor.Context) *gorm.DB { return db.Where("made_country = ?", country) }}) } product.IndexAttrs("-ColorVariations") Admin.AddResource(&models.Color{}, &admin.Config{Menu: []string{"Product Management"}}) Admin.AddResource(&models.Size{}, &admin.Config{Menu: []string{"Product Management"}}) Admin.AddResource(&models.Category{}, &admin.Config{Menu: []string{"Product Management"}}) Admin.AddResource(&models.Order{}, &admin.Config{Menu: []string{"Order Management"}}) store := Admin.AddResource(&models.Store{}, &admin.Config{Menu: []string{"Store Management"}}) store.IndexAttrs("-Latitude", "-Longitude") store.AddValidator(func(record interface{}, metaValues *resource.MetaValues, context *qor.Context) error { if meta := metaValues.Get("Name"); meta != nil { if name := utils.ToString(meta.Value); strings.TrimSpace(name) == "" { return validations.NewError(record, "Name", "Name can't be blank") } } return nil }) Admin.AddResource(config.Config.I18n, &admin.Config{Menu: []string{"Site Management"}}) user := Admin.AddResource(&models.User{}) user.IndexAttrs("ID", "Email", "Name", "Gender", "Role") Admin.AddResource(db.Publish) }
func (res *Resource) finder(result interface{}, metaValues *resource.MetaValues, context *qor.Context) error { var primaryKey string if metaValues == nil { primaryKey = context.ResourceID } else if id := metaValues.Get(res.PrimaryFieldName()); id != nil { primaryKey = utils.ToString(id.Value) } if primaryKey != "" { if metaValues != nil { if destroy := metaValues.Get("_destroy"); destroy != nil { if fmt.Sprintf("%v", destroy.Value) != "0" { context.GetDB().Delete(result, primaryKey) return resource.ErrProcessorSkipLeft } } } return context.GetDB().First(result, primaryKey).Error } return nil }
// ConfigureQorMeta configure slug for qor admin func (Slug) ConfigureQorMeta(meta resource.Metaor) { if meta, ok := meta.(*admin.Meta); ok { res := meta.GetBaseResource().(*admin.Resource) res.GetAdmin().RegisterViewPath("github.com/qor/slug/views") res.UseTheme("slug") name := strings.TrimSuffix(meta.Name, "WithSlug") if meta := res.GetMeta(name); meta != nil { meta.Type = "slug" } else { res.Meta(&admin.Meta{Name: name, Type: "slug"}) } var fieldName = meta.Name res.AddValidator(func(record interface{}, metaValues *resource.MetaValues, context *qor.Context) error { if meta := metaValues.Get(fieldName); meta != nil { slug := utils.ToString(metaValues.Get(fieldName).Value) if slug == "" { return validations.NewError(record, fieldName, name+"'s slug can't be blank") } else if strings.Contains(slug, " ") { return validations.NewError(record, fieldName, name+"'s slug can't contains blank string") } } else { if field, ok := context.GetDB().NewScope(record).FieldByName(fieldName); ok && field.IsBlank { return validations.NewError(record, fieldName, name+"'s slug can't be blank") } } return nil }) res.IndexAttrs(res.IndexAttrs(), "-"+fieldName) res.ShowAttrs(res.ShowAttrs(), "-"+fieldName, false) res.EditAttrs(res.EditAttrs(), "-"+fieldName) res.NewAttrs(res.NewAttrs(), "-"+fieldName) } }
func init() { Admin = admin.New(&qor.Config{DB: db.Publish.DraftDB()}) Admin.SetSiteName("Qor DEMO") Admin.SetAuth(Auth{}) // Add Dashboard Admin.AddMenu(&admin.Menu{Name: "Dashboard", Link: "/admin"}) // Add Asset Manager, for rich editor assetManager := Admin.AddResource(&admin.AssetManager{}, &admin.Config{Invisible: true}) // Add Product product := Admin.AddResource(&models.Product{}, &admin.Config{Menu: []string{"Product Management"}}) product.Meta(&admin.Meta{Name: "MadeCountry", Type: "select_one", Collection: Countries}) product.Meta(&admin.Meta{Name: "Description", Type: "rich_editor", Resource: assetManager}) sizeVariation := Admin.NewResource(&models.SizeVariation{}, &admin.Config{Invisible: true}) sizeVariation.NewAttrs("-ColorVariation") sizeVariation.EditAttrs("-ColorVariation") colorVariation := Admin.NewResource(&models.ColorVariation{}, &admin.Config{Invisible: true}) colorVariation.Meta(&admin.Meta{Name: "SizeVariations", Resource: sizeVariation}) colorVariation.NewAttrs("-Product") colorVariation.EditAttrs("-Product") product.Meta(&admin.Meta{Name: "ColorVariations", Resource: colorVariation}) product.SearchAttrs("Name", "Code", "Category.Name", "Brand.Name") for _, country := range Countries { var country = country product.Scope(&admin.Scope{Name: country, Group: "Made Country", Handle: func(db *gorm.DB, ctx *qor.Context) *gorm.DB { return db.Where("made_country = ?", country) }}) } product.IndexAttrs("-ColorVariations") Admin.AddResource(&models.Color{}, &admin.Config{Menu: []string{"Product Management"}}) Admin.AddResource(&models.Size{}, &admin.Config{Menu: []string{"Product Management"}}) Admin.AddResource(&models.Category{}, &admin.Config{Menu: []string{"Product Management"}}) Admin.AddResource(&models.Collection{}, &admin.Config{Menu: []string{"Product Management"}}) // Add Order orderItem := Admin.NewResource(&models.OrderItem{}) orderItem.Meta(&admin.Meta{Name: "SizeVariation", Type: "select_one", Collection: sizeVariationCollection}) order := Admin.AddResource(&models.Order{}, &admin.Config{Menu: []string{"Order Management"}}) order.Meta(&admin.Meta{Name: "ShippingAddress", Type: "single_edit"}) order.Meta(&admin.Meta{Name: "BillingAddress", Type: "single_edit"}) order.Meta(&admin.Meta{Name: "OrderItems", Resource: orderItem}) activity.RegisterActivityMeta(order) // define scopes for Order for _, state := range []string{"checkout", "cancelled", "paid", "paid_cancelled", "processing", "shipped", "returned"} { var state = state order.Scope(&admin.Scope{ Name: state, Label: strings.Title(strings.Replace(state, "_", " ", -1)), Group: "Order Status", Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB { return db.Where(models.Order{Transition: transition.Transition{State: state}}) }, }) } order.IndexAttrs("-DiscountValue", "-OrderItems", "-AbandonedReason") order.NewAttrs("-DiscountValue", "-AbandonedReason") order.EditAttrs("-DiscountValue", "-AbandonedReason") order.ShowAttrs("-DiscountValue", "-AbandonedReason") order.SearchAttrs("User.Name", "User.Email", "ShippingAddress.ContactName", "ShippingAddress.Address1", "ShippingAddress.Address2") // Define another resource for same model abandonedOrder := Admin.AddResource(&models.Order{}, &admin.Config{Name: "Abandoned Order", Menu: []string{"Order Management"}}) abandonedOrder.Meta(&admin.Meta{Name: "ShippingAddress", Type: "single_edit"}) abandonedOrder.Meta(&admin.Meta{Name: "BillingAddress", Type: "single_edit"}) // Define default scope for abandoned orders abandonedOrder.Scope(&admin.Scope{ Default: true, Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB { return db.Where("abandoned_reason IS NOT NULL AND abandoned_reason <> ?", "") }, }) // Define scopes for abandoned orders for _, amount := range []int{5000, 10000, 20000} { var amount = amount abandonedOrder.Scope(&admin.Scope{ Name: fmt.Sprint(amount), Group: "Amount Greater Than", Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB { return db.Where("payment_amount > ?", amount) }, }) } abandonedOrder.IndexAttrs("-ShippingAddress", "-BillingAddress", "-DiscountValue", "-OrderItems") abandonedOrder.NewAttrs("-DiscountValue") abandonedOrder.EditAttrs("-DiscountValue") abandonedOrder.ShowAttrs("-DiscountValue") // Add Store store := Admin.AddResource(&models.Store{}, &admin.Config{Menu: []string{"Store Management"}}) store.AddValidator(func(record interface{}, metaValues *resource.MetaValues, context *qor.Context) error { if meta := metaValues.Get("Name"); meta != nil { if name := utils.ToString(meta.Value); strings.TrimSpace(name) == "" { return validations.NewError(record, "Name", "Name can't be blank") } } return nil }) // Add Translations Admin.AddResource(config.Config.I18n, &admin.Config{Menu: []string{"Site Management"}}) // Add Setting Admin.AddResource(&models.Setting{}, &admin.Config{Singleton: true}) // Add User user := Admin.AddResource(&models.User{}) user.IndexAttrs("ID", "Email", "Name", "Gender", "Role") // Add Publish Admin.AddResource(db.Publish, &admin.Config{Singleton: true}) // Add Seo Admin.AddResource(&models.Seo{}, &admin.Config{Name: "Meta Data", Singleton: true}) // Add Search Center Admin.AddSearchResource(order, user, product) initFuncMap() initRouter() }
func (meta *Meta) updateMeta() { if meta.Name == "" { utils.ExitWithMsg("Meta should have name: %v", reflect.ValueOf(meta).Type()) } else if meta.Alias == "" { meta.Alias = meta.Name } if meta.Label == "" { meta.Label = utils.HumanizeString(meta.Name) } var ( scope = &gorm.Scope{Value: meta.base.Value} nestedField = strings.Contains(meta.Alias, ".") field *gorm.StructField hasColumn bool ) if nestedField { subModel, name := utils.ParseNestedField(reflect.ValueOf(meta.base.Value), meta.Alias) subScope := &gorm.Scope{Value: subModel.Interface()} field, hasColumn = getField(subScope.GetStructFields(), name) } else { if field, hasColumn = getField(scope.GetStructFields(), meta.Alias); hasColumn { meta.Alias = field.Name if field.IsNormal { meta.DBName = field.DBName } } } var fieldType reflect.Type if hasColumn { fieldType = field.Struct.Type for fieldType.Kind() == reflect.Ptr { fieldType = fieldType.Elem() } } // Set Meta Type if meta.Type == "" && hasColumn { if relationship := field.Relationship; relationship != nil { if relationship.Kind == "has_one" { meta.Type = "single_edit" } else if relationship.Kind == "has_many" { meta.Type = "collection_edit" } else if relationship.Kind == "belongs_to" { meta.Type = "select_one" } else if relationship.Kind == "many_to_many" { meta.Type = "select_many" } } else { switch fieldType.Kind().String() { case "string": if size, ok := utils.ParseTagOption(field.Tag.Get("sql"))["SIZE"]; ok { if i, _ := strconv.Atoi(size); i > 255 { meta.Type = "text" } else { meta.Type = "string" } } else if text, ok := utils.ParseTagOption(field.Tag.Get("sql"))["TYPE"]; ok && text == "text" { meta.Type = "text" } else { meta.Type = "string" } case "bool": meta.Type = "checkbox" default: if regexp.MustCompile(`^(.*)?(u)?(int)(\d+)?`).MatchString(fieldType.Kind().String()) { meta.Type = "number" } else if regexp.MustCompile(`^(.*)?(float)(\d+)?`).MatchString(fieldType.Kind().String()) { meta.Type = "float" } else if _, ok := reflect.New(fieldType).Interface().(*time.Time); ok { meta.Type = "datetime" } else if _, ok := reflect.New(fieldType).Interface().(media_library.MediaLibrary); ok { meta.Type = "file" } } } } // Set Meta Resource if meta.Resource == nil { if hasColumn && (field.Relationship != nil) { var result interface{} if fieldType.Kind().String() == "struct" { result = reflect.New(fieldType).Interface() } else if fieldType.Kind().String() == "slice" { refelectType := fieldType.Elem() for refelectType.Kind() == reflect.Ptr { refelectType = refelectType.Elem() } result = reflect.New(refelectType).Interface() } res := meta.base.GetAdmin().NewResource(result) res.compile() meta.Resource = res } } // Set Meta Valuer if meta.Valuer == nil { if hasColumn { meta.Valuer = func(value interface{}, context *qor.Context) interface{} { scope := context.GetDB().NewScope(value) alias := meta.Alias if nestedField { fields := strings.Split(alias, ".") alias = fields[len(fields)-1] } if f, ok := scope.FieldByName(alias); ok { if field.Relationship != nil { if f.Field.CanAddr() && !scope.PrimaryKeyZero() { context.GetDB().Model(value).Related(f.Field.Addr().Interface(), meta.Alias) } } if f.Field.CanAddr() { return f.Field.Addr().Interface() } else { return f.Field.Interface() } } return "" } } else { utils.ExitWithMsg("Unsupported meta name %v for resource %v", meta.Name, reflect.TypeOf(meta.base.Value)) } } scopeField, _ := scope.FieldByName(meta.Alias) // Set Meta Collection if meta.Collection != nil { if maps, ok := meta.Collection.([]string); ok { meta.GetCollection = func(interface{}, *qor.Context) (results [][]string) { for _, value := range maps { results = append(results, []string{value, value}) } return } } else if maps, ok := meta.Collection.([][]string); ok { meta.GetCollection = func(interface{}, *qor.Context) [][]string { return maps } } else if f, ok := meta.Collection.(func(interface{}, *qor.Context) [][]string); ok { meta.GetCollection = f } else { utils.ExitWithMsg("Unsupported Collection format for meta %v of resource %v", meta.Name, reflect.TypeOf(meta.base.Value)) } } else if meta.Type == "select_one" || meta.Type == "select_many" { if scopeField.Relationship != nil { fieldType := scopeField.StructField.Struct.Type if fieldType.Kind() == reflect.Slice { fieldType = fieldType.Elem() } meta.GetCollection = func(value interface{}, context *qor.Context) (results [][]string) { values := reflect.New(reflect.SliceOf(fieldType)).Interface() context.GetDB().Find(values) reflectValues := reflect.Indirect(reflect.ValueOf(values)) for i := 0; i < reflectValues.Len(); i++ { scope := scope.New(reflectValues.Index(i).Interface()) primaryKey := fmt.Sprintf("%v", scope.PrimaryKeyValue()) results = append(results, []string{primaryKey, utils.Stringify(reflectValues.Index(i).Interface())}) } return } } else { utils.ExitWithMsg("%v meta type %v needs Collection", meta.Name, meta.Type) } } if meta.Setter == nil && hasColumn { if relationship := field.Relationship; relationship != nil { if meta.Type == "select_one" || meta.Type == "select_many" { meta.Setter = func(resource interface{}, metaValue *resource.MetaValue, context *qor.Context) { scope := &gorm.Scope{Value: resource} reflectValue := reflect.Indirect(reflect.ValueOf(resource)) field := reflectValue.FieldByName(meta.Alias) if field.Kind() == reflect.Ptr { if field.IsNil() { field.Set(utils.NewValue(field.Type()).Elem()) } for field.Kind() == reflect.Ptr { field = field.Elem() } } primaryKeys := utils.ToArray(metaValue.Value) // associations not changed for belongs to if relationship.Kind == "belongs_to" && len(relationship.ForeignFieldNames) == 1 { oldPrimaryKeys := utils.ToArray(reflectValue.FieldByName(relationship.ForeignFieldNames[0]).Interface()) // if not changed if equalAsString(primaryKeys, oldPrimaryKeys) { return } // if removed if len(primaryKeys) == 0 { field := reflectValue.FieldByName(relationship.ForeignFieldNames[0]) field.Set(reflect.Zero(field.Type())) } } if len(primaryKeys) > 0 { context.GetDB().Where(primaryKeys).Find(field.Addr().Interface()) } // Replace many 2 many relations if relationship.Kind == "many_to_many" { if !scope.PrimaryKeyZero() { context.GetDB().Model(resource).Association(meta.Alias).Replace(field.Interface()) field.Set(reflect.Zero(field.Type())) } } } } } else { meta.Setter = func(resource interface{}, metaValue *resource.MetaValue, context *qor.Context) { if metaValue == nil { return } value := metaValue.Value alias := meta.Alias if nestedField { fields := strings.Split(alias, ".") alias = fields[len(fields)-1] } field := reflect.Indirect(reflect.ValueOf(resource)).FieldByName(alias) if field.Kind() == reflect.Ptr { if field.IsNil() { field.Set(utils.NewValue(field.Type()).Elem()) } for field.Kind() == reflect.Ptr { field = field.Elem() } } if field.IsValid() && field.CanAddr() { switch field.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: field.SetInt(utils.ToInt(value)) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: field.SetUint(utils.ToUint(value)) case reflect.Float32, reflect.Float64: field.SetFloat(utils.ToFloat(value)) case reflect.Bool: // TODO: add test if utils.ToString(value) == "true" { field.SetBool(true) } else { field.SetBool(false) } default: if scanner, ok := field.Addr().Interface().(sql.Scanner); ok { if scanner.Scan(value) != nil { scanner.Scan(utils.ToString(value)) } } else if reflect.TypeOf("").ConvertibleTo(field.Type()) { field.Set(reflect.ValueOf(utils.ToString(value)).Convert(field.Type())) } else if reflect.TypeOf([]string{}).ConvertibleTo(field.Type()) { field.Set(reflect.ValueOf(utils.ToArray(value)).Convert(field.Type())) } else if rvalue := reflect.ValueOf(value); reflect.TypeOf(rvalue.Type()).ConvertibleTo(field.Type()) { field.Set(rvalue.Convert(field.Type())) } else if _, ok := field.Addr().Interface().(*time.Time); ok { if str := utils.ToString(value); str != "" { if newTime, err := now.Parse(str); err == nil { field.Set(reflect.ValueOf(newTime)) } } } else { var buf = bytes.NewBufferString("") json.NewEncoder(buf).Encode(value) if err := json.NewDecoder(strings.NewReader(buf.String())).Decode(field.Addr().Interface()); err != nil { utils.ExitWithMsg("Can't set value %v to %v [meta %v]", reflect.ValueOf(value).Type(), field.Type(), meta) } } } } } } } if nestedField { oldvalue := meta.Valuer meta.Valuer = func(value interface{}, context *qor.Context) interface{} { return oldvalue(utils.GetNestedModel(value, meta.Alias, context), context) } oldSetter := meta.Setter meta.Setter = func(resource interface{}, metaValue *resource.MetaValue, context *qor.Context) { oldSetter(utils.GetNestedModel(resource, meta.Alias, context), metaValue, context) } } }
func init() { Admin = admin.New(&qor.Config{DB: db.Publish.DraftDB()}) Admin.SetSiteName("Qor DEMO") Admin.SetAuth(Auth{}) // Add Dashboard Admin.AddMenu(&admin.Menu{Name: "Dashboard", Link: "/admin"}) // Add Asset Manager, for rich editor assetManager := Admin.AddResource(&media_library.AssetManager{}, &admin.Config{Invisible: true}) // Add Product product := Admin.AddResource(&models.Product{}, &admin.Config{Menu: []string{"Product Management"}}) product.Meta(&admin.Meta{Name: "MadeCountry", Type: "select_one", Collection: Countries}) product.Meta(&admin.Meta{Name: "Description", Type: "rich_editor", Resource: assetManager}) colorVariationMeta := product.Meta(&admin.Meta{Name: "ColorVariations"}) colorVariation := colorVariationMeta.Resource colorVariation.NewAttrs("-Product") colorVariation.EditAttrs("-Product") sizeVariationMeta := colorVariation.Meta(&admin.Meta{Name: "SizeVariations"}) sizeVariation := sizeVariationMeta.Resource sizeVariation.NewAttrs("-ColorVariation") sizeVariation.EditAttrs( &admin.Section{ Rows: [][]string{ {"Size", "AvailableQuantity"}, }, }, ) product.SearchAttrs("Name", "Code", "Category.Name", "Brand.Name") product.EditAttrs( &admin.Section{ Title: "Basic Information", Rows: [][]string{ {"Name"}, {"Code", "Price"}, {"Disabled"}, }}, &admin.Section{ Title: "Organization", Rows: [][]string{ {"Category", "Collections", "MadeCountry"}, }}, "Description", "ColorVariations", ) for _, country := range Countries { var country = country product.Scope(&admin.Scope{Name: country, Group: "Made Country", Handle: func(db *gorm.DB, ctx *qor.Context) *gorm.DB { return db.Where("made_country = ?", country) }}) } product.IndexAttrs("-ColorVariations") product.Action(&admin.Action{ Name: "disable", Handle: func(arg *admin.ActionArgument) error { for _, record := range arg.FindSelectedRecords() { arg.Context.DB.Model(record.(*models.Product)).Update("disabled", true) } return nil }, Visibles: []string{"menu_item"}, }) product.Action(&admin.Action{ Name: "enable", Handle: func(arg *admin.ActionArgument) error { for _, record := range arg.FindSelectedRecords() { arg.Context.DB.Model(record.(*models.Product)).Update("disabled", false) } return nil }, Visibles: []string{"menu_item"}, }) Admin.AddResource(&models.Color{}, &admin.Config{Menu: []string{"Product Management"}}) Admin.AddResource(&models.Size{}, &admin.Config{Menu: []string{"Product Management"}}) Admin.AddResource(&models.Category{}, &admin.Config{Menu: []string{"Product Management"}}) Admin.AddResource(&models.Collection{}, &admin.Config{Menu: []string{"Product Management"}}) // Add Order order := Admin.AddResource(&models.Order{}, &admin.Config{Menu: []string{"Order Management"}}) order.Meta(&admin.Meta{Name: "ShippingAddress", Type: "single_edit"}) order.Meta(&admin.Meta{Name: "BillingAddress", Type: "single_edit"}) order.Meta(&admin.Meta{Name: "ShippedAt", Type: "date"}) orderItemMeta := order.Meta(&admin.Meta{Name: "OrderItems"}) orderItemMeta.Resource.Meta(&admin.Meta{Name: "SizeVariation", Type: "select_one", Collection: sizeVariationCollection}) // define scopes for Order for _, state := range []string{"checkout", "cancelled", "paid", "paid_cancelled", "processing", "shipped", "returned"} { var state = state order.Scope(&admin.Scope{ Name: state, Label: strings.Title(strings.Replace(state, "_", " ", -1)), Group: "Order Status", Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB { return db.Where(models.Order{Transition: transition.Transition{State: state}}) }, }) } // define actions for Order type trackingNumberArgument struct { TrackingNumber string } order.Action(&admin.Action{ Name: "Ship", Handle: func(argument *admin.ActionArgument) error { trackingNumberArgument := argument.Argument.(*trackingNumberArgument) for _, record := range argument.FindSelectedRecords() { argument.Context.GetDB().Model(record).UpdateColumn("tracking_number", trackingNumberArgument.TrackingNumber) } return nil }, Resource: Admin.NewResource(&trackingNumberArgument{}), Visibles: []string{"menu_item"}, }) order.Action(&admin.Action{ Name: "Cancel", Handle: func(argument *admin.ActionArgument) error { for _, order := range argument.FindSelectedRecords() { db := argument.Context.GetDB() if err := models.OrderState.Trigger("cancel", order.(*models.Order), db); err != nil { return err } db.Select("state").Save(order) } return nil }, Visibles: []string{"menu_item"}, }) order.IndexAttrs("User", "PaymentAmount", "ShippedAt", "CancelledAt", "State", "ShippingAddress") order.NewAttrs("-DiscountValue", "-AbandonedReason", "-CancelledAt") order.EditAttrs("-DiscountValue", "-AbandonedReason", "-CancelledAt") order.SearchAttrs("User.Name", "User.Email", "ShippingAddress.ContactName", "ShippingAddress.Address1", "ShippingAddress.Address2") // Define another resource for same model abandonedOrder := Admin.AddResource(&models.Order{}, &admin.Config{Name: "Abandoned Order", Menu: []string{"Order Management"}}) abandonedOrder.Meta(&admin.Meta{Name: "ShippingAddress", Type: "single_edit"}) abandonedOrder.Meta(&admin.Meta{Name: "BillingAddress", Type: "single_edit"}) // Define default scope for abandoned orders abandonedOrder.Scope(&admin.Scope{ Default: true, Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB { return db.Where("abandoned_reason IS NOT NULL AND abandoned_reason <> ?", "") }, }) // Define scopes for abandoned orders for _, amount := range []int{5000, 10000, 20000} { var amount = amount abandonedOrder.Scope(&admin.Scope{ Name: fmt.Sprint(amount), Group: "Amount Greater Than", Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB { return db.Where("payment_amount > ?", amount) }, }) } abandonedOrder.IndexAttrs("-ShippingAddress", "-BillingAddress", "-DiscountValue", "-OrderItems") abandonedOrder.NewAttrs("-DiscountValue") abandonedOrder.EditAttrs("-DiscountValue") abandonedOrder.ShowAttrs("-DiscountValue") // Add Store store := Admin.AddResource(&models.Store{}, &admin.Config{Menu: []string{"Store Management"}}) store.AddValidator(func(record interface{}, metaValues *resource.MetaValues, context *qor.Context) error { if meta := metaValues.Get("Name"); meta != nil { if name := utils.ToString(meta.Value); strings.TrimSpace(name) == "" { return validations.NewError(record, "Name", "Name can't be blank") } } return nil }) // Add Translations Admin.AddResource(config.Config.I18n, &admin.Config{Menu: []string{"Site Management"}}) // Add Setting Admin.AddResource(&models.Setting{}, &admin.Config{Singleton: true}) // Add User user := Admin.AddResource(&models.User{}) user.IndexAttrs("ID", "Email", "Name", "Gender", "Role") // Add Publish Admin.AddResource(db.Publish, &admin.Config{Singleton: true}) // Add Worker Worker := getWorker() Admin.AddResource(Worker) exchange_actions.RegisterExchangeJobs(config.Config.I18n, Worker) initFuncMap() initRouter() }
func (mediaBox MediaBox) ConfigureQorMeta(metaor resource.Metaor) { if meta, ok := metaor.(*admin.Meta); ok { if meta.Config == nil { meta.Config = &MediaBoxConfig{} } if meta.FormattedValuer == nil { meta.FormattedValuer = func(record interface{}, context *qor.Context) interface{} { if mediaBox, ok := meta.GetValuer()(record, context).(*MediaBox); ok { return mediaBox.URL() } return "" } meta.SetFormattedValuer(meta.FormattedValuer) } if config, ok := meta.Config.(*MediaBoxConfig); ok { Admin := meta.GetBaseResource().(*admin.Resource).GetAdmin() if config.RemoteDataResource == nil { mediaLibraryResource := Admin.GetResource("MediaLibrary") if mediaLibraryResource == nil { mediaLibraryResource = Admin.NewResource(&MediaLibrary{}) } config.RemoteDataResource = mediaLibraryResource } if _, ok := config.RemoteDataResource.Value.(MediaLibraryInterface); !ok { utils.ExitWithMsg("%v havn't implement MediaLibraryInterface, please fix that.", reflect.TypeOf(config.RemoteDataResource.Value)) } if meta := config.RemoteDataResource.GetMeta("MediaOption"); meta == nil { config.RemoteDataResource.Meta(&admin.Meta{ Name: "MediaOption", Type: "hidden", Setter: func(record interface{}, metaValue *resource.MetaValue, context *qor.Context) { if mediaLibrary, ok := record.(MediaLibraryInterface); ok { var mediaOption MediaOption if err := json.Unmarshal([]byte(utils.ToString(metaValue.Value)), &mediaOption); err == nil { mediaOption.FileName = "" mediaOption.URL = "" mediaOption.OriginalURL = "" mediaLibrary.ScanMediaOptions(mediaOption) } } }, Valuer: func(record interface{}, context *qor.Context) interface{} { if mediaLibrary, ok := record.(MediaLibraryInterface); ok { if value, err := json.Marshal(mediaLibrary.GetMediaOption()); err == nil { return string(value) } } return "" }, }) } if meta := config.RemoteDataResource.GetMeta("SelectedType"); meta == nil { config.RemoteDataResource.Meta(&admin.Meta{ Name: "SelectedType", Type: "hidden", Valuer: func(record interface{}, context *qor.Context) interface{} { if mediaLibrary, ok := record.(MediaLibraryInterface); ok { return mediaLibrary.GetSelectedType() } return "" }, }) } config.RemoteDataResource.AddProcessor(func(record interface{}, metaValues *resource.MetaValues, context *qor.Context) error { if mediaLibrary, ok := record.(MediaLibraryInterface); ok { var filename string var mediaOption MediaOption for _, metaValue := range metaValues.Values { if fileHeaders, ok := metaValue.Value.([]*multipart.FileHeader); ok { for _, fileHeader := range fileHeaders { filename = fileHeader.Filename } } } if metaValue := metaValues.Get("MediaOption"); metaValue != nil { mediaOptionStr := utils.ToString(metaValue.Value) json.Unmarshal([]byte(mediaOptionStr), &mediaOption) } if mediaOption.SelectedType == "video_link" { mediaLibrary.SetSelectedType("video_link") } else if filename != "" { if _, err := getImageFormat(filename); err == nil { mediaLibrary.SetSelectedType("image") } else if isVideoFormat(filename) { mediaLibrary.SetSelectedType("video") } else { mediaLibrary.SetSelectedType("file") } } } return nil }) config.RemoteDataResource.UseTheme("grid") config.RemoteDataResource.UseTheme("media_library") config.RemoteDataResource.IndexAttrs(config.RemoteDataResource.IndexAttrs(), "-MediaOption") config.RemoteDataResource.NewAttrs(config.RemoteDataResource.NewAttrs(), "MediaOption") config.RemoteDataResource.EditAttrs(config.RemoteDataResource.EditAttrs(), "MediaOption") config.SelectManyConfig.RemoteDataResource = config.RemoteDataResource config.SelectManyConfig.ConfigureQorMeta(meta) } meta.Type = "media_box" } }
func (meta *Meta) Initialize() error { var ( nestedField = strings.Contains(meta.FieldName, ".") field = meta.FieldStruct hasColumn = meta.FieldStruct != nil ) var fieldType reflect.Type if hasColumn { fieldType = field.Struct.Type for fieldType.Kind() == reflect.Ptr { fieldType = fieldType.Elem() } } // Set Meta Valuer if meta.Valuer == nil { if hasColumn { meta.Valuer = func(value interface{}, context *qor.Context) interface{} { scope := context.GetDB().NewScope(value) fieldName := meta.FieldName if nestedField { fields := strings.Split(fieldName, ".") fieldName = fields[len(fields)-1] } if f, ok := scope.FieldByName(fieldName); ok { if f.Relationship != nil && f.Field.CanAddr() && !scope.PrimaryKeyZero() { context.GetDB().Model(value).Related(f.Field.Addr().Interface(), meta.FieldName) } return f.Field.Interface() } return "" } } else { utils.ExitWithMsg("Unsupported meta name %v for resource %v", meta.FieldName, reflect.TypeOf(meta.Resource.GetResource().Value)) } } if meta.Setter == nil && hasColumn { if relationship := field.Relationship; relationship != nil { if relationship.Kind == "belongs_to" || relationship.Kind == "many_to_many" { meta.Setter = func(resource interface{}, metaValue *MetaValue, context *qor.Context) { scope := &gorm.Scope{Value: resource} reflectValue := reflect.Indirect(reflect.ValueOf(resource)) field := reflectValue.FieldByName(meta.FieldName) if field.Kind() == reflect.Ptr { if field.IsNil() { field.Set(utils.NewValue(field.Type()).Elem()) } for field.Kind() == reflect.Ptr { field = field.Elem() } } primaryKeys := utils.ToArray(metaValue.Value) // associations not changed for belongs to if relationship.Kind == "belongs_to" && len(relationship.ForeignFieldNames) == 1 { oldPrimaryKeys := utils.ToArray(reflectValue.FieldByName(relationship.ForeignFieldNames[0]).Interface()) // if not changed if fmt.Sprint(primaryKeys) == fmt.Sprint(oldPrimaryKeys) { return } // if removed if len(primaryKeys) == 0 { field := reflectValue.FieldByName(relationship.ForeignFieldNames[0]) field.Set(reflect.Zero(field.Type())) } } if len(primaryKeys) > 0 { context.GetDB().Where(primaryKeys).Find(field.Addr().Interface()) } // Replace many 2 many relations if relationship.Kind == "many_to_many" { if !scope.PrimaryKeyZero() { context.GetDB().Model(resource).Association(meta.FieldName).Replace(field.Interface()) field.Set(reflect.Zero(field.Type())) } } } } } else { meta.Setter = func(resource interface{}, metaValue *MetaValue, context *qor.Context) { if metaValue == nil { return } value := metaValue.Value fieldName := meta.FieldName if nestedField { fields := strings.Split(fieldName, ".") fieldName = fields[len(fields)-1] } field := reflect.Indirect(reflect.ValueOf(resource)).FieldByName(fieldName) if field.Kind() == reflect.Ptr { if field.IsNil() { field.Set(utils.NewValue(field.Type()).Elem()) } for field.Kind() == reflect.Ptr { field = field.Elem() } } if field.IsValid() && field.CanAddr() { switch field.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: field.SetInt(utils.ToInt(value)) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: field.SetUint(utils.ToUint(value)) case reflect.Float32, reflect.Float64: field.SetFloat(utils.ToFloat(value)) case reflect.Bool: // TODO: add test if utils.ToString(value) == "true" { field.SetBool(true) } else { field.SetBool(false) } default: if scanner, ok := field.Addr().Interface().(sql.Scanner); ok { if scanner.Scan(value) != nil { scanner.Scan(utils.ToString(value)) } } else if reflect.TypeOf("").ConvertibleTo(field.Type()) { field.Set(reflect.ValueOf(utils.ToString(value)).Convert(field.Type())) } else if reflect.TypeOf([]string{}).ConvertibleTo(field.Type()) { field.Set(reflect.ValueOf(utils.ToArray(value)).Convert(field.Type())) } else if rvalue := reflect.ValueOf(value); reflect.TypeOf(rvalue.Type()).ConvertibleTo(field.Type()) { field.Set(rvalue.Convert(field.Type())) } else if _, ok := field.Addr().Interface().(*time.Time); ok { if str := utils.ToString(value); str != "" { if newTime, err := now.Parse(str); err == nil { field.Set(reflect.ValueOf(newTime)) } } } else { var buf = bytes.NewBufferString("") json.NewEncoder(buf).Encode(value) if err := json.NewDecoder(strings.NewReader(buf.String())).Decode(field.Addr().Interface()); err != nil { utils.ExitWithMsg("Can't set value %v to %v [meta %v]", reflect.TypeOf(value), field.Type(), meta) } } } } } } } if nestedField { oldvalue := meta.Valuer meta.Valuer = func(value interface{}, context *qor.Context) interface{} { return oldvalue(getNestedModel(value, meta.FieldName, context), context) } oldSetter := meta.Setter meta.Setter = func(resource interface{}, metaValue *MetaValue, context *qor.Context) { oldSetter(getNestedModel(resource, meta.FieldName, context), metaValue, context) } } return nil }
func SetupAdmin() *admin.Admin { // Setup Database for QOR Admin sorting.RegisterCallbacks(config.DB) validations.RegisterCallbacks(config.DB) media_library.RegisterCallbacks(config.DB) result := admin.New(&qor.Config{DB: config.DB}) result.SetSiteName(config.QOR.SiteName) result.SetAuth(config.Auth) //result.RegisterViewPath("/home/drew/GoWork/src/github.com/8legd/hugocms/vendor/github.com/qor/admin/views") // Add Asset Manager, for rich editor assetManager := result.AddResource(&media_library.AssetManager{}, &admin.Config{Invisible: true}) columnImage := result.NewResource(&models.PageContentColumnImage{}, &admin.Config{Invisible: true}) columnImage.Meta(&admin.Meta{ Name: "Alignment", Type: "select_one", Collection: func(o interface{}, context *qor.Context) [][]string { var result [][]string result = append(result, []string{"media-left media-top", "left top"}) result = append(result, []string{"media-left media-middle", "left middle"}) result = append(result, []string{"media-left media-bottom", "left bottom"}) result = append(result, []string{"media-right media-top", "right top"}) result = append(result, []string{"media-right media-middle", "right middle"}) result = append(result, []string{"media-right media-bottom", "right bottom"}) return result }, }) columnImage.NewAttrs("-ContentColumns") columnImage.EditAttrs("-ContentColumns") columns := result.NewResource(&models.PageContentColumn{}, &admin.Config{Invisible: true}) columns.Meta(&admin.Meta{ Name: "ColumnWidth", Type: "select_one", Collection: func(o interface{}, context *qor.Context) [][]string { var result [][]string result = append(result, []string{"col-md-6", "50% on desktop, 100% on mobile"}) result = append(result, []string{"col-md-12", "100% on desktop, 100% on mobile"}) return result }, }) columns.Meta(&admin.Meta{Name: "ColumnText", Type: "rich_editor", Resource: assetManager}) staticContentSection := &admin.Section{ Title: "Static Content", Rows: [][]string{ {"ColumnText"}, {"ColumnImage"}, }} columns.Meta(&admin.Meta{Name: "ColumnImage", Resource: columnImage}) dynmamicContentSection := &admin.Section{ Title: "Dynamic Content", Rows: [][]string{ {"Video", "Slideshow"}, }} columns.NewAttrs("-Page", "ColumnWidth", "ColumnHeading", staticContentSection, dynmamicContentSection, "ColumnLink") columns.EditAttrs("-Page", "ColumnWidth", "ColumnHeading", staticContentSection, dynmamicContentSection, "ColumnLink") links := result.NewResource(&models.PageLink{}, &admin.Config{Invisible: true}) links.Meta(&admin.Meta{Name: "LinkText", Type: "rich_editor", Resource: assetManager}) links.NewAttrs("-Page") links.EditAttrs("-Page") pages = result.AddResource(&models.Page{}, &admin.Config{Name: "Pages"}) pages.IndexAttrs("Path", "Name") pages.Meta(&admin.Meta{Name: "ContentColumns", Resource: columns}) pages.Meta(&admin.Meta{Name: "Links", Resource: links}) pages.Meta(&admin.Meta{Name: "Path", Type: "select_one", Collection: config.QOR.Paths}) // define scopes for pages for _, path := range config.QOR.Paths { path := path // The anonymous function below captures the variable `path` not its value // So because the range variable is re-assigned a value on each iteration, if we just used it, // the actual value being used would just end up being the same (last value of iteration). // By redeclaring `path` within the range block's scope a new variable is in effect created for each iteration // and that specific variable is used in the anonymous function instead // Another solution would be to pass the range variable into a function as a parameter which then returns the // original function you wanted creating a `closure` around the passed in parameter (you often come accross this in JavaScript) pages.Scope(&admin.Scope{ Name: path, Group: "Path", Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB { return db.Where(models.Page{Path: path}) }, }) } pageSection := &admin.Section{ Title: "Page Setup", Rows: [][]string{ {"Name"}, {"Path", "MenuWeight"}, {"Links"}, }} pages.NewAttrs(pageSection, "SEO", "ContentColumns") pages.EditAttrs(pageSection, "SEO", "ContentColumns") pages.AddValidator(func(record interface{}, metaValues *resource.MetaValues, context *qor.Context) error { if meta := metaValues.Get("Name"); meta != nil { if name := utils.ToString(meta.Value); strings.TrimSpace(name) == "" { return validations.NewError(record, "Name", "Name can not be blank") } } if meta := metaValues.Get("Path"); meta != nil { if name := utils.ToString(meta.Value); strings.TrimSpace(name) == "" { return validations.NewError(record, "Path", "Path can not be blank") } } // TODO make SEO required // if we have content check it is valid if meta := metaValues.Get("ContentColumns"); meta != nil { if metas := meta.MetaValues.Values; len(metas) > 0 { for _, v := range metas { // All image content need alt text and alignment if v.Name == "ImageContent" { if fields := v.MetaValues.Values; len(fields) > 0 { img := false imgAlt := false imgAlign := false for _, f := range fields { if f.Name == "Image" && f.Value != nil { if v, ok := f.Value.([]*multipart.FileHeader); ok { if len(v) > 0 { img = true } } } if f.Name == "Alt" && f.Value != nil { if v, ok := f.Value.([]string); ok { if len(v) > 0 && v[0] != "" { imgAlt = true } } } if f.Name == "Alignment" && f.Value != nil { if v, ok := f.Value.([]string); ok { if len(v) > 0 && v[0] != "" { imgAlign = true } } } } if img && (!imgAlt || !imgAlign) { return validations.NewError(record, "ContentColumns", "All Image Content requires Alt Text and Alignment") } } } } } } return nil }) slideshows = result.AddResource(&models.Slideshow{}, &admin.Config{Name: "Slideshow"}) slideshows.IndexAttrs("Name") videos = result.AddResource(&models.Video{}, &admin.Config{Name: "Videos"}) videos.IndexAttrs("Name") // Add Settings contact := result.NewResource(&models.SettingsContactDetails{}, &admin.Config{Invisible: true}) contact.Meta(&admin.Meta{Name: "OpeningHoursDesktop", Type: "rich_editor", Resource: assetManager}) callToAction := result.NewResource(&models.SettingsCallToAction{}, &admin.Config{Invisible: true}) callToAction.Meta(&admin.Meta{Name: "ActionText", Type: "rich_editor", Resource: assetManager}) settings = result.AddResource(&models.Settings{}, &admin.Config{Singleton: true}) settings.Meta(&admin.Meta{Name: "ContactDetails", Resource: contact}) settings.Meta(&admin.Meta{Name: "CallToAction", Resource: callToAction}) settings.Meta(&admin.Meta{Name: "Footer", Type: "rich_editor", Resource: assetManager}) releases = result.AddResource(&models.Release{}, &admin.Config{ Name: "Releases", Permission: roles.Deny(roles.Delete, roles.Anyone), }) releases.IndexAttrs("ID", "Date", "Comment") releases.NewAttrs("Comment") releases.EditAttrs("") releases.ShowAttrs("ID", "Date", "Comment", "Log") // Add Translations result.AddResource(config.I18n, &admin.Config{}) return result }
func init() { Admin = admin.New(&qor.Config{DB: db.DB.Set("publish:draft_mode", true)}) Admin.SetSiteName("Qor DEMO") Admin.SetAuth(auth.AdminAuth{}) Admin.SetAssetFS(bindatafs.AssetFS) // Add Dashboard Admin.AddMenu(&admin.Menu{Name: "Dashboard", Link: "/admin"}) // Add Asset Manager, for rich editor assetManager := Admin.AddResource(&media_library.AssetManager{}, &admin.Config{Invisible: true}) // Add Product product := Admin.AddResource(&models.Product{}, &admin.Config{Menu: []string{"Product Management"}}) product.Meta(&admin.Meta{Name: "MadeCountry", Type: "select_one", Collection: Countries}) product.Meta(&admin.Meta{Name: "Description", Type: "rich_editor", Resource: assetManager}) colorVariationMeta := product.Meta(&admin.Meta{Name: "ColorVariations"}) colorVariation := colorVariationMeta.Resource colorVariation.NewAttrs("-Product") colorVariation.EditAttrs("-Product") sizeVariationMeta := colorVariation.Meta(&admin.Meta{Name: "SizeVariations"}) sizeVariation := sizeVariationMeta.Resource sizeVariation.NewAttrs("-ColorVariation") sizeVariation.EditAttrs( &admin.Section{ Rows: [][]string{ {"Size", "AvailableQuantity"}, }, }, ) product.SearchAttrs("Name", "Code", "Category.Name", "Brand.Name") product.EditAttrs( &admin.Section{ Title: "Basic Information", Rows: [][]string{ {"Name"}, {"Code", "Price"}, {"Enabled"}, }}, &admin.Section{ Title: "Organization", Rows: [][]string{ {"Category", "MadeCountry"}, {"Collections"}, }}, "Description", "ColorVariations", ) for _, country := range Countries { var country = country product.Scope(&admin.Scope{Name: country, Group: "Made Country", Handle: func(db *gorm.DB, ctx *qor.Context) *gorm.DB { return db.Where("made_country = ?", country) }}) } product.IndexAttrs("-ColorVariations") product.Action(&admin.Action{ Name: "View On Site", URL: func(record interface{}, context *admin.Context) string { if product, ok := record.(*models.Product); ok { return fmt.Sprintf("/products/%v", product.Code) } return "#" }, Modes: []string{"menu_item", "edit"}, }) product.Action(&admin.Action{ Name: "Disable", Handle: func(arg *admin.ActionArgument) error { for _, record := range arg.FindSelectedRecords() { arg.Context.DB.Model(record.(*models.Product)).Update("enabled", false) } return nil }, Visible: func(record interface{}, context *admin.Context) bool { if product, ok := record.(*models.Product); ok { return product.Enabled == true } return true }, Modes: []string{"index", "edit", "menu_item"}, }) product.Action(&admin.Action{ Name: "Enable", Handle: func(arg *admin.ActionArgument) error { for _, record := range arg.FindSelectedRecords() { arg.Context.DB.Model(record.(*models.Product)).Update("enabled", true) } return nil }, Visible: func(record interface{}, context *admin.Context) bool { if product, ok := record.(*models.Product); ok { return product.Enabled == false } return true }, Modes: []string{"index", "edit", "menu_item"}, }) Admin.AddResource(&models.Color{}, &admin.Config{Menu: []string{"Product Management"}}) Admin.AddResource(&models.Size{}, &admin.Config{Menu: []string{"Product Management"}}) Admin.AddResource(&models.Category{}, &admin.Config{Menu: []string{"Product Management"}}) Admin.AddResource(&models.Collection{}, &admin.Config{Menu: []string{"Product Management"}}) // Add Order order := Admin.AddResource(&models.Order{}, &admin.Config{Menu: []string{"Order Management"}}) order.Meta(&admin.Meta{Name: "ShippingAddress", Type: "single_edit"}) order.Meta(&admin.Meta{Name: "BillingAddress", Type: "single_edit"}) order.Meta(&admin.Meta{Name: "ShippedAt", Type: "date"}) orderItemMeta := order.Meta(&admin.Meta{Name: "OrderItems"}) orderItemMeta.Resource.Meta(&admin.Meta{Name: "SizeVariation", Type: "select_one", Collection: sizeVariationCollection}) orderItemMeta.Resource.NewAttrs("-State") orderItemMeta.Resource.EditAttrs("-State") // define scopes for Order for _, state := range []string{"checkout", "cancelled", "paid", "paid_cancelled", "processing", "shipped", "returned"} { var state = state order.Scope(&admin.Scope{ Name: state, Label: strings.Title(strings.Replace(state, "_", " ", -1)), Group: "Order Status", Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB { return db.Where(models.Order{Transition: transition.Transition{State: state}}) }, }) } // define actions for Order type trackingNumberArgument struct { TrackingNumber string } order.Action(&admin.Action{ Name: "Processing", Handle: func(argument *admin.ActionArgument) error { for _, order := range argument.FindSelectedRecords() { db := argument.Context.GetDB() if err := models.OrderState.Trigger("process", order.(*models.Order), db); err != nil { return err } db.Select("state").Save(order) } return nil }, Visible: func(record interface{}, context *admin.Context) bool { if order, ok := record.(*models.Order); ok { return order.State == "paid" } return false }, Modes: []string{"show", "menu_item"}, }) order.Action(&admin.Action{ Name: "Ship", Handle: func(argument *admin.ActionArgument) error { var ( tx = argument.Context.GetDB().Begin() trackingNumberArgument = argument.Argument.(*trackingNumberArgument) ) if trackingNumberArgument.TrackingNumber != "" { for _, record := range argument.FindSelectedRecords() { order := record.(*models.Order) order.TrackingNumber = &trackingNumberArgument.TrackingNumber models.OrderState.Trigger("ship", order, tx, "tracking number "+trackingNumberArgument.TrackingNumber) if err := tx.Save(order).Error; err != nil { tx.Rollback() return err } } } else { return errors.New("invalid shipment number") } tx.Commit() return nil }, Visible: func(record interface{}, context *admin.Context) bool { if order, ok := record.(*models.Order); ok { return order.State == "processing" } return false }, Resource: Admin.NewResource(&trackingNumberArgument{}), Modes: []string{"show", "menu_item"}, }) order.Action(&admin.Action{ Name: "Cancel", Handle: func(argument *admin.ActionArgument) error { for _, order := range argument.FindSelectedRecords() { db := argument.Context.GetDB() if err := models.OrderState.Trigger("cancel", order.(*models.Order), db); err != nil { return err } db.Select("state").Save(order) } return nil }, Visible: func(record interface{}, context *admin.Context) bool { if order, ok := record.(*models.Order); ok { for _, state := range []string{"draft", "checkout", "paid", "processing"} { if order.State == state { return true } } } return false }, Modes: []string{"index", "show", "menu_item"}, }) order.IndexAttrs("User", "PaymentAmount", "ShippedAt", "CancelledAt", "State", "ShippingAddress") order.NewAttrs("-DiscountValue", "-AbandonedReason", "-CancelledAt") order.EditAttrs("-DiscountValue", "-AbandonedReason", "-CancelledAt", "-State") order.ShowAttrs("-DiscountValue", "-State") order.SearchAttrs("User.Name", "User.Email", "ShippingAddress.ContactName", "ShippingAddress.Address1", "ShippingAddress.Address2") // Add activity for order activity.Register(order) // Define another resource for same model abandonedOrder := Admin.AddResource(&models.Order{}, &admin.Config{Name: "Abandoned Order", Menu: []string{"Order Management"}}) abandonedOrder.Meta(&admin.Meta{Name: "ShippingAddress", Type: "single_edit"}) abandonedOrder.Meta(&admin.Meta{Name: "BillingAddress", Type: "single_edit"}) // Define default scope for abandoned orders abandonedOrder.Scope(&admin.Scope{ Default: true, Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB { return db.Where("abandoned_reason IS NOT NULL AND abandoned_reason <> ?", "") }, }) // Define scopes for abandoned orders for _, amount := range []int{5000, 10000, 20000} { var amount = amount abandonedOrder.Scope(&admin.Scope{ Name: fmt.Sprint(amount), Group: "Amount Greater Than", Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB { return db.Where("payment_amount > ?", amount) }, }) } abandonedOrder.IndexAttrs("-ShippingAddress", "-BillingAddress", "-DiscountValue", "-OrderItems") abandonedOrder.NewAttrs("-DiscountValue") abandonedOrder.EditAttrs("-DiscountValue") abandonedOrder.ShowAttrs("-DiscountValue") // Add Store store := Admin.AddResource(&models.Store{}, &admin.Config{Menu: []string{"Store Management"}}) store.AddValidator(func(record interface{}, metaValues *resource.MetaValues, context *qor.Context) error { if meta := metaValues.Get("Name"); meta != nil { if name := utils.ToString(meta.Value); strings.TrimSpace(name) == "" { return validations.NewError(record, "Name", "Name can't be blank") } } return nil }) // Add Translations Admin.AddResource(i18n.I18n, &admin.Config{Menu: []string{"Site Management"}}) // Add SEOSetting Admin.AddResource(&models.SEOSetting{}, &admin.Config{Menu: []string{"Site Management"}, Singleton: true}) // Add Setting Admin.AddResource(&models.Setting{}, &admin.Config{Singleton: true}) // Add User user := Admin.AddResource(&models.User{}) user.Meta(&admin.Meta{Name: "Gender", Type: "select_one", Collection: []string{"Male", "Female", "Unknown"}}) user.IndexAttrs("ID", "Email", "Name", "Gender", "Role") user.ShowAttrs( &admin.Section{ Title: "Basic Information", Rows: [][]string{ {"Name"}, {"Email", "Password"}, {"Gender", "Role"}, }}, "Addresses", ) user.EditAttrs(user.ShowAttrs()) // Add Worker Worker := getWorker() Admin.AddResource(Worker) db.Publish.SetWorker(Worker) exchange_actions.RegisterExchangeJobs(i18n.I18n, Worker) // Add Publish Admin.AddResource(db.Publish, &admin.Config{Singleton: true}) publish.RegisterL10nForPublish(db.Publish, Admin) // Add Search Center Resources Admin.AddSearchResource(product, user, order) // Add ActionBar ActionBar = action_bar.New(Admin, auth.AdminAuth{}) ActionBar.RegisterAction(&action_bar.Action{Name: "Admin Dashboard", Link: "/admin"}) initFuncMap() initRouter() }
func init() { Admin = admin.New(&qor.Config{DB: db.DB.Set("publish:draft_mode", true)}) Admin.SetSiteName("Qor DEMO") Admin.SetAuth(auth.AdminAuth{}) Admin.SetAssetFS(bindatafs.AssetFS) // Add Notification Notification := notification.New(¬ification.Config{}) Notification.RegisterChannel(database.New(&database.Config{DB: db.DB})) Notification.Action(¬ification.Action{ Name: "Confirm", Visible: func(data *notification.QorNotification, context *admin.Context) bool { return data.ResolvedAt == nil }, MessageTypes: []string{"order_returned"}, Handle: func(argument *notification.ActionArgument) error { orderID := regexp.MustCompile(`#(\d+)`).FindStringSubmatch(argument.Message.Body)[1] err := argument.Context.GetDB().Model(&models.Order{}).Where("id = ? AND returned_at IS NULL", orderID).Update("returned_at", time.Now()).Error if err == nil { return argument.Context.GetDB().Model(argument.Message).Update("resolved_at", time.Now()).Error } return err }, Undo: func(argument *notification.ActionArgument) error { orderID := regexp.MustCompile(`#(\d+)`).FindStringSubmatch(argument.Message.Body)[1] err := argument.Context.GetDB().Model(&models.Order{}).Where("id = ? AND returned_at IS NOT NULL", orderID).Update("returned_at", nil).Error if err == nil { return argument.Context.GetDB().Model(argument.Message).Update("resolved_at", nil).Error } return err }, }) Notification.Action(¬ification.Action{ Name: "Check it out", MessageTypes: []string{"order_paid_cancelled", "order_processed", "order_returned"}, URL: func(data *notification.QorNotification, context *admin.Context) string { return path.Join("/admin/orders/", regexp.MustCompile(`#(\d+)`).FindStringSubmatch(data.Body)[1]) }, }) Notification.Action(¬ification.Action{ Name: "Dismiss", MessageTypes: []string{"order_paid_cancelled", "info", "order_processed", "order_returned"}, Visible: func(data *notification.QorNotification, context *admin.Context) bool { return data.ResolvedAt == nil }, Handle: func(argument *notification.ActionArgument) error { return argument.Context.GetDB().Model(argument.Message).Update("resolved_at", time.Now()).Error }, Undo: func(argument *notification.ActionArgument) error { return argument.Context.GetDB().Model(argument.Message).Update("resolved_at", nil).Error }, }) Admin.NewResource(Notification) // Add Dashboard Admin.AddMenu(&admin.Menu{Name: "Dashboard", Link: "/admin"}) // Add Asset Manager, for rich editor assetManager := Admin.AddResource(&media_library.AssetManager{}, &admin.Config{Invisible: true}) //* Produc Management *// color := Admin.AddResource(&models.Color{}, &admin.Config{Menu: []string{"Product Management"}, Priority: -5}) Admin.AddResource(&models.Size{}, &admin.Config{Menu: []string{"Product Management"}, Priority: -4}) category := Admin.AddResource(&models.Category{}, &admin.Config{Menu: []string{"Product Management"}, Priority: -3}) Admin.AddResource(&models.Collection{}, &admin.Config{Menu: []string{"Product Management"}, Priority: -2}) // Add ProductImage as Media Libraray ProductImagesResource := Admin.AddResource(&models.ProductImage{}, &admin.Config{Menu: []string{"Product Management"}, Priority: -1}) ProductImagesResource.Filter(&admin.Filter{ Name: "Color", Config: &admin.SelectOneConfig{RemoteDataResource: color}, }) ProductImagesResource.Filter(&admin.Filter{ Name: "Category", Config: &admin.SelectOneConfig{RemoteDataResource: category}, }) ProductImagesResource.IndexAttrs("Image", "Title") // Add Product product := Admin.AddResource(&models.Product{}, &admin.Config{Menu: []string{"Product Management"}}) product.Meta(&admin.Meta{Name: "MadeCountry", Config: &admin.SelectOneConfig{Collection: Countries}}) product.Meta(&admin.Meta{Name: "Description", Config: &admin.RichEditorConfig{AssetManager: assetManager}}) product.Meta(&admin.Meta{Name: "Category", Config: &admin.SelectOneConfig{AllowBlank: true}}) product.Meta(&admin.Meta{Name: "Collections", Config: &admin.SelectManyConfig{SelectMode: "bottom_sheet"}}) product.Meta(&admin.Meta{Name: "MainImage", Config: &media_library.MediaBoxConfig{ RemoteDataResource: ProductImagesResource, Max: 1, Sizes: map[string]media_library.Size{ "preview": {Width: 300, Height: 300}, }, }}) product.Meta(&admin.Meta{Name: "MainImageURL", Valuer: func(record interface{}, context *qor.Context) interface{} { if p, ok := record.(*models.Product); ok { result := bytes.NewBufferString("") tmpl, _ := template.New("").Parse("<img src='{{.image}}'></img>") tmpl.Execute(result, map[string]string{"image": p.MainImageURL()}) return template.HTML(result.String()) } return "" }}) product.UseTheme("grid") colorVariationMeta := product.Meta(&admin.Meta{Name: "ColorVariations"}) colorVariation := colorVariationMeta.Resource colorVariation.Meta(&admin.Meta{Name: "Images", Config: &media_library.MediaBoxConfig{ RemoteDataResource: ProductImagesResource, Sizes: map[string]media_library.Size{ "icon": {Width: 50, Height: 50}, "preview": {Width: 300, Height: 300}, "listing": {Width: 640, Height: 640}, }, }}) colorVariation.NewAttrs("-Product", "-ColorCode") colorVariation.EditAttrs("-Product", "-ColorCode") sizeVariationMeta := colorVariation.Meta(&admin.Meta{Name: "SizeVariations"}) sizeVariation := sizeVariationMeta.Resource sizeVariation.NewAttrs("-ColorVariation") sizeVariation.EditAttrs( &admin.Section{ Rows: [][]string{ {"Size", "AvailableQuantity"}, }, }, ) product.SearchAttrs("Name", "Code", "Category.Name", "Brand.Name") product.IndexAttrs("MainImageURL", "Name", "Price") product.EditAttrs( &admin.Section{ Title: "Basic Information", Rows: [][]string{ {"Name"}, {"Code", "Price"}, {"MainImage"}, }}, &admin.Section{ Title: "Organization", Rows: [][]string{ {"Category", "MadeCountry"}, {"Collections"}, }}, "ProductProperties", "Description", "ColorVariations", ) product.NewAttrs(product.EditAttrs()) for _, country := range Countries { var country = country product.Scope(&admin.Scope{Name: country, Group: "Made Country", Handle: func(db *gorm.DB, ctx *qor.Context) *gorm.DB { return db.Where("made_country = ?", country) }}) } product.Action(&admin.Action{ Name: "View On Site", URL: func(record interface{}, context *admin.Context) string { if product, ok := record.(*models.Product); ok { return fmt.Sprintf("/products/%v", product.Code) } return "#" }, Modes: []string{"menu_item", "edit"}, }) product.Action(&admin.Action{ Name: "Disable", Handle: func(arg *admin.ActionArgument) error { for _, record := range arg.FindSelectedRecords() { arg.Context.DB.Model(record.(*models.Product)).Update("enabled", false) } return nil }, Visible: func(record interface{}, context *admin.Context) bool { if product, ok := record.(*models.Product); ok { return product.Enabled == true } return true }, Modes: []string{"index", "edit", "menu_item"}, }) product.Action(&admin.Action{ Name: "Enable", Handle: func(arg *admin.ActionArgument) error { for _, record := range arg.FindSelectedRecords() { arg.Context.DB.Model(record.(*models.Product)).Update("enabled", true) } return nil }, Visible: func(record interface{}, context *admin.Context) bool { if product, ok := record.(*models.Product); ok { return product.Enabled == false } return true }, Modes: []string{"index", "edit", "menu_item"}, }) // Add Order order := Admin.AddResource(&models.Order{}, &admin.Config{Menu: []string{"Order Management"}}) order.Meta(&admin.Meta{Name: "ShippingAddress", Type: "single_edit"}) order.Meta(&admin.Meta{Name: "BillingAddress", Type: "single_edit"}) order.Meta(&admin.Meta{Name: "ShippedAt", Type: "date"}) orderItemMeta := order.Meta(&admin.Meta{Name: "OrderItems"}) orderItemMeta.Resource.Meta(&admin.Meta{Name: "SizeVariation", Config: &admin.SelectOneConfig{Collection: sizeVariationCollection}}) // define scopes for Order for _, state := range []string{"checkout", "cancelled", "paid", "paid_cancelled", "processing", "shipped", "returned"} { var state = state order.Scope(&admin.Scope{ Name: state, Label: strings.Title(strings.Replace(state, "_", " ", -1)), Group: "Order Status", Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB { return db.Where(models.Order{Transition: transition.Transition{State: state}}) }, }) } // define actions for Order type trackingNumberArgument struct { TrackingNumber string } order.Action(&admin.Action{ Name: "Processing", Handle: func(argument *admin.ActionArgument) error { for _, order := range argument.FindSelectedRecords() { db := argument.Context.GetDB() if err := models.OrderState.Trigger("process", order.(*models.Order), db); err != nil { return err } db.Select("state").Save(order) } return nil }, Visible: func(record interface{}, context *admin.Context) bool { if order, ok := record.(*models.Order); ok { return order.State == "paid" } return false }, Modes: []string{"show", "menu_item"}, }) order.Action(&admin.Action{ Name: "Ship", Handle: func(argument *admin.ActionArgument) error { var ( tx = argument.Context.GetDB().Begin() trackingNumberArgument = argument.Argument.(*trackingNumberArgument) ) if trackingNumberArgument.TrackingNumber != "" { for _, record := range argument.FindSelectedRecords() { order := record.(*models.Order) order.TrackingNumber = &trackingNumberArgument.TrackingNumber models.OrderState.Trigger("ship", order, tx, "tracking number "+trackingNumberArgument.TrackingNumber) if err := tx.Save(order).Error; err != nil { tx.Rollback() return err } } } else { return errors.New("invalid shipment number") } tx.Commit() return nil }, Visible: func(record interface{}, context *admin.Context) bool { if order, ok := record.(*models.Order); ok { return order.State == "processing" } return false }, Resource: Admin.NewResource(&trackingNumberArgument{}), Modes: []string{"show", "menu_item"}, }) order.Action(&admin.Action{ Name: "Cancel", Handle: func(argument *admin.ActionArgument) error { for _, order := range argument.FindSelectedRecords() { db := argument.Context.GetDB() if err := models.OrderState.Trigger("cancel", order.(*models.Order), db); err != nil { return err } db.Select("state").Save(order) } return nil }, Visible: func(record interface{}, context *admin.Context) bool { if order, ok := record.(*models.Order); ok { for _, state := range []string{"draft", "checkout", "paid", "processing"} { if order.State == state { return true } } } return false }, Modes: []string{"index", "show", "menu_item"}, }) order.IndexAttrs("User", "PaymentAmount", "ShippedAt", "CancelledAt", "State", "ShippingAddress") order.NewAttrs("-DiscountValue", "-AbandonedReason", "-CancelledAt") order.EditAttrs("-DiscountValue", "-AbandonedReason", "-CancelledAt", "-State") order.ShowAttrs("-DiscountValue", "-State") order.SearchAttrs("User.Name", "User.Email", "ShippingAddress.ContactName", "ShippingAddress.Address1", "ShippingAddress.Address2") // Add activity for order activity.Register(order) // Define another resource for same model abandonedOrder := Admin.AddResource(&models.Order{}, &admin.Config{Name: "Abandoned Order", Menu: []string{"Order Management"}}) abandonedOrder.Meta(&admin.Meta{Name: "ShippingAddress", Type: "single_edit"}) abandonedOrder.Meta(&admin.Meta{Name: "BillingAddress", Type: "single_edit"}) // Define default scope for abandoned orders abandonedOrder.Scope(&admin.Scope{ Default: true, Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB { return db.Where("abandoned_reason IS NOT NULL AND abandoned_reason <> ?", "") }, }) // Define scopes for abandoned orders for _, amount := range []int{5000, 10000, 20000} { var amount = amount abandonedOrder.Scope(&admin.Scope{ Name: fmt.Sprint(amount), Group: "Amount Greater Than", Handle: func(db *gorm.DB, context *qor.Context) *gorm.DB { return db.Where("payment_amount > ?", amount) }, }) } abandonedOrder.IndexAttrs("-ShippingAddress", "-BillingAddress", "-DiscountValue", "-OrderItems") abandonedOrder.NewAttrs("-DiscountValue") abandonedOrder.EditAttrs("-DiscountValue") abandonedOrder.ShowAttrs("-DiscountValue") // Add User user := Admin.AddResource(&models.User{}, &admin.Config{Menu: []string{"User Management"}}) user.Meta(&admin.Meta{Name: "Gender", Config: &admin.SelectOneConfig{Collection: []string{"Male", "Female", "Unknown"}}}) user.Meta(&admin.Meta{Name: "Role", Config: &admin.SelectOneConfig{Collection: []string{"Admin", "Maintainer", "Member"}}}) user.Meta(&admin.Meta{Name: "Password", Type: "password", FormattedValuer: func(interface{}, *qor.Context) interface{} { return "" }, Setter: func(resource interface{}, metaValue *resource.MetaValue, context *qor.Context) { values := metaValue.Value.([]string) if len(values) > 0 { if newPassword := values[0]; newPassword != "" { bcryptPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) if err != nil { context.DB.AddError(validations.NewError(user, "Password", "Can't encrpt password")) return } u := resource.(*models.User) u.Password = string(bcryptPassword) } } }, }) user.Meta(&admin.Meta{Name: "Confirmed", Valuer: func(user interface{}, ctx *qor.Context) interface{} { if user.(*models.User).ID == 0 { return true } return user.(*models.User).Confirmed }}) user.Filter(&admin.Filter{ Name: "Role", Config: &admin.SelectOneConfig{ Collection: []string{"Admin", "Maintainer", "Member"}, }, }) user.IndexAttrs("ID", "Email", "Name", "Gender", "Role") user.ShowAttrs( &admin.Section{ Title: "Basic Information", Rows: [][]string{ {"Name"}, {"Email", "Password"}, {"Gender", "Role"}, {"Confirmed"}, }}, "Addresses", ) user.EditAttrs(user.ShowAttrs()) // Add Store store := Admin.AddResource(&models.Store{}, &admin.Config{Menu: []string{"Store Management"}}) store.Meta(&admin.Meta{Name: "Owner", Type: "single_edit"}) store.AddValidator(func(record interface{}, metaValues *resource.MetaValues, context *qor.Context) error { if meta := metaValues.Get("Name"); meta != nil { if name := utils.ToString(meta.Value); strings.TrimSpace(name) == "" { return validations.NewError(record, "Name", "Name can't be blank") } } return nil }) // Add Translations Admin.AddResource(i18n.I18n, &admin.Config{Menu: []string{"Site Management"}, Priority: 1}) // Add SEOSetting Admin.AddResource(&models.SEOSetting{}, &admin.Config{Menu: []string{"Site Management"}, Singleton: true, Priority: 2}) // Add Worker Worker := getWorker() Admin.AddResource(Worker, &admin.Config{Menu: []string{"Site Management"}}) db.Publish.SetWorker(Worker) exchange_actions.RegisterExchangeJobs(i18n.I18n, Worker) // Add Publish Admin.AddResource(db.Publish, &admin.Config{Menu: []string{"Site Management"}, Singleton: true}) publish.RegisterL10nForPublish(db.Publish, Admin) // Add Setting Admin.AddResource(&models.Setting{}, &admin.Config{Name: "Shop Setting", Singleton: true}) // Add Search Center Resources Admin.AddSearchResource(product, user, order) // Add ActionBar ActionBar = action_bar.New(Admin, auth.AdminAuth{}) ActionBar.RegisterAction(&action_bar.Action{Name: "Admin Dashboard", Link: "/admin"}) initWidgets() initFuncMap() initRouter() }
// Register register activity feature for an qor resource func Register(res *admin.Resource) { var ( qorAdmin = res.GetAdmin() activityResource = qorAdmin.GetResource("QorActivity") ) if activityResource == nil { // Auto run migration before add resource res.GetAdmin().Config.DB.AutoMigrate(&QorActivity{}) activityResource = qorAdmin.AddResource(&QorActivity{}, &admin.Config{Invisible: true}) activityResource.Meta(&admin.Meta{Name: "Action", Type: "hidden", Valuer: func(value interface{}, ctx *qor.Context) interface{} { act := value.(*QorActivity) if act.Action == "" { act.Action = "comment on" } return activityResource.GetAdmin().T(ctx, "activity."+act.Action, act.Action) }}) activityResource.Meta(&admin.Meta{Name: "UpdatedAt", Type: "hidden", Valuer: func(value interface{}, ctx *qor.Context) interface{} { return utils.FormatTime(value.(*QorActivity).UpdatedAt, "Jan 2 15:04", ctx) }}) activityResource.Meta(&admin.Meta{Name: "URL", Valuer: func(value interface{}, ctx *qor.Context) interface{} { return fmt.Sprintf("/admin/%v/%v/!%v/%v/edit", res.ToParam(), res.GetPrimaryValue(ctx.Request), activityResource.ToParam(), value.(*QorActivity).ID) }}) assetManager := qorAdmin.GetResource("AssetManager") if assetManager == nil { assetManager = qorAdmin.AddResource(&media_library.AssetManager{}, &admin.Config{Invisible: true}) } activityResource.Meta(&admin.Meta{Name: "Content", Type: "rich_editor", Resource: assetManager}) activityResource.Meta(&admin.Meta{Name: "Note", Type: "string", Resource: assetManager}) activityResource.EditAttrs("Action", "Content", "Note") activityResource.ShowAttrs("ID", "Action", "Content", "Note", "URL", "UpdatedAt", "CreatorName") activityResource.AddValidator(func(record interface{}, metaValues *resource.MetaValues, context *qor.Context) error { if meta := metaValues.Get("Content"); meta != nil { if name := utils.ToString(meta.Value); strings.TrimSpace(name) == "" { return validations.NewError(record, "Content", "Content can't be blank") } } return nil }) } res.GetAdmin().RegisterViewPath("github.com/qor/activity/views") res.UseTheme("activity") qorAdmin.RegisterFuncMap("activity_resource", func() *admin.Resource { return qorAdmin.GetResource("QorActivity") }) qorAdmin.RegisterFuncMap("get_activities_count", func(context *admin.Context) int { return GetActivitiesCount(context) }) router := res.GetAdmin().GetRouter() ctrl := controller{ActivityResource: activityResource} router.Get(fmt.Sprintf("/%v/%v/!qor_activities", res.ToParam(), res.ParamIDName()), ctrl.GetActivity) router.Post(fmt.Sprintf("/%v/%v/!qor_activities", res.ToParam(), res.ParamIDName()), ctrl.CreateActivity) router.Post(fmt.Sprintf("/%v/%v/!qor_activities/%v/edit", res.ToParam(), res.ParamIDName(), activityResource.ParamIDName()), ctrl.UpdateActivity) }
// ConfigureQorResourceBeforeInitialize configure qor resource for qor admin func (serialize *SerializableMeta) ConfigureQorResourceBeforeInitialize(res resource.Resourcer) { if res, ok := res.(*admin.Resource); ok { res.GetAdmin().RegisterViewPath("github.com/qor/serializable_meta/views") if _, ok := res.Value.(SerializableMetaInterface); ok { if res.GetMeta("Kind") == nil { res.Meta(&admin.Meta{ Name: "Kind", Type: "hidden", Valuer: func(value interface{}, context *qor.Context) interface{} { defer func() { if r := recover(); r != nil { utils.ExitWithMsg("SerializableMeta: Can't Get Kind") } }() return value.(SerializableMetaInterface).GetSerializableArgumentKind() }, Setter: func(value interface{}, metaValue *resource.MetaValue, context *qor.Context) { value.(SerializableMetaInterface).SetSerializableArgumentKind(utils.ToString(metaValue.Value)) }, }) } if res.GetMeta("SerializableMeta") == nil { res.Meta(&admin.Meta{ Name: "SerializableMeta", Type: "serializable_meta", Valuer: func(value interface{}, context *qor.Context) interface{} { if serializeArgument, ok := value.(SerializableMetaInterface); ok { return struct { Value interface{} Resource *admin.Resource }{ Value: serializeArgument.GetSerializableArgument(serializeArgument), Resource: serializeArgument.GetSerializableArgumentResource(), } } return nil }, FormattedValuer: func(value interface{}, context *qor.Context) interface{} { if serializeArgument, ok := value.(SerializableMetaInterface); ok { return serializeArgument.GetSerializableArgument(serializeArgument) } return nil }, Setter: func(result interface{}, metaValue *resource.MetaValue, context *qor.Context) { if serializeArgument, ok := result.(SerializableMetaInterface); ok { if serializeArgumentResource := serializeArgument.GetSerializableArgumentResource(); serializeArgumentResource != nil { var clearUpRecord, fillUpRecord func(record interface{}, metaors []resource.Metaor, metaValues []*resource.MetaValue) // Keep original value, so if user don't have permission to update some fields, we won't lost the data value := serializeArgument.GetSerializableArgument(serializeArgument) for _, fc := range serializeArgumentResource.Validators { context.AddError(fc(value, metaValue.MetaValues, context)) } // Clear all nested slices if has related form data clearUpRecord = func(record interface{}, metaors []resource.Metaor, metaValues []*resource.MetaValue) { for _, meta := range metaors { for _, metaValue := range metaValues { if meta.GetName() == metaValue.Name { if metaResource, ok := meta.GetResource().(*admin.Resource); ok && metaResource != nil && metaValue.MetaValues != nil { nestedFieldValue := reflect.Indirect(reflect.ValueOf(record)).FieldByName(meta.GetFieldName()) if nestedFieldValue.Kind() == reflect.Struct { clearUpRecord(nestedFieldValue.Addr().Interface(), metaResource.GetMetas([]string{}), metaValue.MetaValues.Values) } else if nestedFieldValue.Kind() == reflect.Slice { nestedFieldValue.Set(reflect.Zero(nestedFieldValue.Type())) } } } } } } clearUpRecord(value, serializeArgumentResource.GetMetas([]string{}), metaValue.MetaValues.Values) fillUpRecord = func(record interface{}, metaors []resource.Metaor, metaValues []*resource.MetaValue) { for _, meta := range metaors { for _, metaValue := range metaValues { if meta.GetName() == metaValue.Name { if metaResource, ok := meta.GetResource().(*admin.Resource); ok && metaResource != nil && metaValue.MetaValues != nil { nestedFieldValue := reflect.Indirect(reflect.ValueOf(record)).FieldByName(meta.GetFieldName()) if nestedFieldValue.Kind() == reflect.Struct { nestedValue := nestedFieldValue.Addr().Interface() for _, fc := range metaResource.Validators { context.AddError(fc(nestedValue, metaValue.MetaValues, context)) } fillUpRecord(nestedValue, metaResource.GetMetas([]string{}), metaValue.MetaValues.Values) for _, fc := range metaResource.Processors { context.AddError(fc(nestedValue, metaValue.MetaValues, context)) } } if nestedFieldValue.Kind() == reflect.Slice { nestedValue := reflect.New(nestedFieldValue.Type().Elem()) for _, fc := range metaResource.Validators { context.AddError(fc(nestedValue, metaValue.MetaValues, context)) } if destroy := metaValue.MetaValues.Get("_destroy"); destroy == nil || fmt.Sprint(destroy.Value) == "0" { fillUpRecord(nestedValue.Interface(), metaResource.GetMetas([]string{}), metaValue.MetaValues.Values) if !reflect.DeepEqual(reflect.Zero(nestedFieldValue.Type().Elem()).Interface(), nestedValue.Elem().Interface()) { nestedFieldValue.Set(reflect.Append(nestedFieldValue, nestedValue.Elem())) for _, fc := range metaResource.Processors { context.AddError(fc(nestedValue, metaValue.MetaValues, context)) } } } } continue } if setter := meta.GetSetter(); setter != nil { setter(record, metaValue, context) continue } } } } } fillUpRecord(value, serializeArgumentResource.GetMetas([]string{}), metaValue.MetaValues.Values) for _, fc := range serializeArgumentResource.Processors { context.AddError(fc(value, metaValue.MetaValues, context)) } serializeArgument.SetSerializableArgumentValue(value) } } }, }) } res.NewAttrs("Kind", "SerializableMeta") res.EditAttrs("ID", "Kind", "SerializableMeta") } } }