func (product Product) Validate(db *gorm.DB) { if strings.TrimSpace(product.Name) == "" { db.AddError(validations.NewError(product, "Name", "Name can not be empty")) } if strings.TrimSpace(product.Code) == "" { db.AddError(validations.NewError(product, "Code", "Code can not be empty")) } }
func (size Size) Validate(db *gorm.DB) { if strings.TrimSpace(size.Name) == "" { db.AddError(validations.NewError(size, "Name", "Name can not be empty")) } if strings.TrimSpace(size.Code) == "" { db.AddError(validations.NewError(size, "Code", "Code can not be empty")) } }
func (color Color) Validate(db *gorm.DB) { if strings.TrimSpace(color.Name) == "" { db.AddError(validations.NewError(color, "Name", "Name can not be empty")) } if strings.TrimSpace(color.Code) == "" { db.AddError(validations.NewError(color, "Code", "Code can not be empty")) } }
func (user *User) Validate(db *gorm.DB) { govalidator.CustomTypeTagMap.Set("uniqEmail", govalidator.CustomTypeValidator(func(email interface{}, context interface{}) bool { var count int if db.Model(&User{}).Where("email = ?", email).Count(&count); count == 0 || email == "" { return true } return false })) if user.Name == "invalid" { db.AddError(validations.NewError(user, "Name", "invalid user name")) } }
func init() { ProductExchange = exchange.NewResource(&models.Product{}, exchange.Config{PrimaryField: "Code"}) ProductExchange.Meta(&exchange.Meta{Name: "Code"}) ProductExchange.Meta(&exchange.Meta{Name: "Name"}) ProductExchange.Meta(&exchange.Meta{Name: "Price"}) ProductExchange.AddValidator(func(record interface{}, metaValues *resource.MetaValues, context *qor.Context) error { if utils.ToInt(metaValues.Get("Price").Value) < 100 { return validations.NewError(record, "Price", "price can't less than 100") } 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(&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 (category Category) Validate(db *gorm.DB) { if strings.TrimSpace(category.Name) == "" { db.AddError(validations.NewError(category, "Name", "Name can not be empty")) } }
func (language *Language) Validate(db *gorm.DB) error { if language.Code == "invalid" { return validations.NewError(language, "Code", "invalid language") } return nil }
func (address *Address) Validate(db *gorm.DB) { if address.Address == "invalid" { db.AddError(validations.NewError(address, "Address", "invalid address")) } }
func (card *CreditCard) Validate(db *gorm.DB) { if !regexp.MustCompile("^(\\d){13,16}$").MatchString(card.Number) { db.AddError(validations.NewError(card, "Number", "invalid card number")) } }
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() }
func (productImage ProductImage) Validate(db *gorm.DB) { if strings.TrimSpace(productImage.Title) == "" { db.AddError(validations.NewError(productImage, "Title", "Tile can not be empty")) } }
// 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) }