Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit cf36a0f
Showing
177 changed files
with
18,387 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package admin | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
|
||
"github.com/qor/qor" | ||
"github.com/qor/qor/utils" | ||
"github.com/qor/roles" | ||
) | ||
|
||
// Action register action for qor resource | ||
func (res *Resource) Action(action *Action) { | ||
if action.Label == "" { | ||
action.Label = utils.HumanizeString(action.Name) | ||
} | ||
res.Actions = append(res.Actions, action) | ||
} | ||
|
||
// ActionArgument action argument that used in handle | ||
type ActionArgument struct { | ||
PrimaryValues []string | ||
Context *Context | ||
Argument interface{} | ||
} | ||
|
||
// Action action definiation | ||
type Action struct { | ||
Name string | ||
Label string | ||
Handle func(arg *ActionArgument) error | ||
Resource *Resource | ||
Permission *roles.Permission | ||
Visibles []string | ||
} | ||
|
||
// ToParam used to register routes for actions | ||
func (action Action) ToParam() string { | ||
return utils.ToParamString(action.Name) | ||
} | ||
|
||
// HasPermission check if current user has permission for the action | ||
func (action Action) HasPermission(mode roles.PermissionMode, context *qor.Context) bool { | ||
if action.Permission == nil { | ||
return true | ||
} | ||
return action.Permission.HasPermission(mode, context.Roles...) | ||
} | ||
|
||
// FindSelectedRecords find selected records when run bulk actions | ||
func (actionArgument *ActionArgument) FindSelectedRecords() []interface{} { | ||
var ( | ||
context = actionArgument.Context | ||
resource = context.Resource | ||
records = []interface{}{} | ||
) | ||
|
||
clone := context.clone() | ||
clone.SetDB(clone.GetDB().Where(fmt.Sprintf("%v IN (?)", resource.PrimaryDBName()), actionArgument.PrimaryValues)) | ||
results, _ := clone.FindMany() | ||
|
||
resultValues := reflect.Indirect(reflect.ValueOf(results)) | ||
for i := 0; i < resultValues.Len(); i++ { | ||
records = append(records, resultValues.Index(i).Interface()) | ||
} | ||
return records | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
package admin | ||
|
||
import ( | ||
"html/template" | ||
"reflect" | ||
|
||
"github.com/jinzhu/inflection" | ||
"github.com/qor/qor" | ||
"github.com/qor/qor/resource" | ||
"github.com/qor/qor/utils" | ||
"github.com/theplant/cldr" | ||
) | ||
|
||
// Admin is a struct that used to generate admin/api interface | ||
type Admin struct { | ||
Config *qor.Config | ||
SiteName string | ||
I18n I18n | ||
menus []*Menu | ||
resources []*Resource | ||
searchResources []*Resource | ||
auth Auth | ||
router *Router | ||
funcMaps template.FuncMap | ||
metaConfigorMaps map[string]func(*Meta) | ||
} | ||
|
||
// ResourceNamer is an interface for models that defined method `ResourceName` | ||
type ResourceNamer interface { | ||
ResourceName() string | ||
} | ||
|
||
// New new admin with configuration | ||
func New(config *qor.Config) *Admin { | ||
admin := Admin{ | ||
funcMaps: make(template.FuncMap), | ||
Config: config, | ||
router: newRouter(), | ||
metaConfigorMaps: metaConfigorMaps, | ||
} | ||
return &admin | ||
} | ||
|
||
// SetSiteName set site's name, the name will be used as admin HTML title and admin interface will auto load javascripts, stylesheets files based on its value | ||
// For example, if you named it as `Qor Demo`, admin will look up `qor_demo.js`, `qor_demo.css` in QOR view paths, and load them if found | ||
func (admin *Admin) SetSiteName(siteName string) { | ||
admin.SiteName = siteName | ||
} | ||
|
||
// SetAuth set admin's authorization gateway | ||
func (admin *Admin) SetAuth(auth Auth) { | ||
admin.auth = auth | ||
} | ||
|
||
// RegisterMetaConfigor register configor for a kind, it will be called when register those kind of metas | ||
func (admin *Admin) RegisterMetaConfigor(kind string, fc func(*Meta)) { | ||
admin.metaConfigorMaps[kind] = fc | ||
} | ||
|
||
// RegisterFuncMap register view funcs, it could be used in view templates | ||
func (admin *Admin) RegisterFuncMap(name string, fc interface{}) { | ||
admin.funcMaps[name] = fc | ||
} | ||
|
||
// GetRouter get router from admin | ||
func (admin *Admin) GetRouter() *Router { | ||
return admin.router | ||
} | ||
|
||
// NewResource initialize a new qor resource, won't add it to admin, just initialize it | ||
func (admin *Admin) NewResource(value interface{}, config ...*Config) *Resource { | ||
var configuration *Config | ||
if len(config) > 0 { | ||
configuration = config[0] | ||
} | ||
|
||
if configuration == nil { | ||
configuration = &Config{} | ||
} | ||
|
||
res := &Resource{ | ||
Resource: *resource.New(value), | ||
Config: configuration, | ||
cachedMetas: &map[string][]*Meta{}, | ||
filters: map[string]*Filter{}, | ||
admin: admin, | ||
} | ||
|
||
res.Permission = configuration.Permission | ||
|
||
if configuration.PageCount == 0 { | ||
configuration.PageCount = 20 | ||
} | ||
|
||
if configuration.Name != "" { | ||
res.Name = configuration.Name | ||
} else if namer, ok := value.(ResourceNamer); ok { | ||
res.Name = namer.ResourceName() | ||
} | ||
|
||
// Configure resource when initializing | ||
modelType := admin.Config.DB.NewScope(res.Value).GetModelStruct().ModelType | ||
for i := 0; i < modelType.NumField(); i++ { | ||
if fieldStruct := modelType.Field(i); fieldStruct.Anonymous { | ||
if injector, ok := reflect.New(fieldStruct.Type).Interface().(resource.ConfigureResourceBeforeInitializeInterface); ok { | ||
injector.ConfigureQorResourceBeforeInitialize(res) | ||
} | ||
} | ||
} | ||
|
||
if injector, ok := res.Value.(resource.ConfigureResourceBeforeInitializeInterface); ok { | ||
injector.ConfigureQorResourceBeforeInitialize(res) | ||
} | ||
|
||
findOneHandler := res.FindOneHandler | ||
res.FindOneHandler = func(result interface{}, metaValues *resource.MetaValues, context *qor.Context) error { | ||
if context.ResourceID == "" { | ||
context.ResourceID = res.GetPrimaryValue(context.Request) | ||
} | ||
return findOneHandler(result, metaValues, context) | ||
} | ||
|
||
return res | ||
} | ||
|
||
// AddResource make a model manageable from admin interface | ||
func (admin *Admin) AddResource(value interface{}, config ...*Config) *Resource { | ||
res := admin.NewResource(value, config...) | ||
|
||
if !res.Config.Invisible { | ||
var menuName string | ||
if res.Config.Singleton { | ||
menuName = res.Name | ||
} else { | ||
menuName = inflection.Plural(res.Name) | ||
} | ||
|
||
menu := &Menu{rawPath: res.ToParam(), Name: menuName} | ||
admin.menus = appendMenu(admin.menus, res.Config.Menu, menu) | ||
} | ||
|
||
admin.resources = append(admin.resources, res) | ||
return res | ||
} | ||
|
||
// AddSearchResource make a resource searchable from search center | ||
func (admin *Admin) AddSearchResource(resources ...*Resource) { | ||
admin.searchResources = append(admin.searchResources, resources...) | ||
} | ||
|
||
func (admin *Admin) EnabledSearchCenter() bool { | ||
return len(admin.searchResources) > 0 | ||
} | ||
|
||
// GetResource get resource with name | ||
func (admin *Admin) GetResource(name string) *Resource { | ||
for _, res := range admin.resources { | ||
var typeName = reflect.Indirect(reflect.ValueOf(res.Value)).Type().String() | ||
if res.ToParam() == name || res.Name == name || typeName == name { | ||
return res | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// GetResources get defined resources from admin | ||
func (admin *Admin) GetResources() []*Resource { | ||
return admin.resources | ||
} | ||
|
||
// I18n define admin's i18n interface | ||
type I18n interface { | ||
Scope(scope string) I18n | ||
Default(value string) I18n | ||
T(locale string, key string, args ...interface{}) template.HTML | ||
} | ||
|
||
// T call i18n backend to translate | ||
func (admin *Admin) T(context *qor.Context, key string, value string, values ...interface{}) template.HTML { | ||
locale := utils.GetLocale(context) | ||
|
||
if admin.I18n == nil { | ||
if result, err := cldr.Parse(locale, value, values...); err == nil { | ||
return template.HTML(result) | ||
} | ||
return template.HTML(key) | ||
} | ||
|
||
return admin.I18n.Default(value).T(locale, key, values...) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package admin | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/qor/qor" | ||
"github.com/qor/qor/test/utils" | ||
) | ||
|
||
type User struct { | ||
Name string | ||
ID uint64 | ||
} | ||
|
||
var db = utils.TestDB() | ||
|
||
func TestAddResource(t *testing.T) { | ||
admin := New(&qor.Config{DB: db}) | ||
user := admin.AddResource(&User{}) | ||
|
||
if user != admin.resources[0] { | ||
t.Error("resource not added") | ||
} | ||
|
||
if admin.GetMenus()[0].Name != "Users" { | ||
t.Error("resource not added to menu") | ||
} | ||
} | ||
|
||
func TestAddResourceWithInvisibleOption(t *testing.T) { | ||
admin := New(&qor.Config{DB: db}) | ||
user := admin.AddResource(&User{}, &Config{Invisible: true}) | ||
|
||
if user != admin.resources[0] { | ||
t.Error("resource not added") | ||
} | ||
|
||
if len(admin.GetMenus()) != 0 { | ||
t.Error("invisible resource registered in menu") | ||
} | ||
} | ||
|
||
func TestGetResource(t *testing.T) { | ||
admin := New(&qor.Config{DB: db}) | ||
user := admin.AddResource(&User{}) | ||
|
||
if admin.GetResource("User") != user { | ||
t.Error("resource not returned") | ||
} | ||
} | ||
|
||
func TestNewResource(t *testing.T) { | ||
admin := New(&qor.Config{DB: db}) | ||
user := admin.NewResource(&User{}) | ||
|
||
if user.Name != "User" { | ||
t.Error("default resource name didn't set") | ||
} | ||
|
||
if user.Config.PageCount != 20 { | ||
t.Error("default page count didn't set") | ||
} | ||
} | ||
|
||
type UserWithCustomizedName struct{} | ||
|
||
func (u *UserWithCustomizedName) ResourceName() string { | ||
return "CustomizedName" | ||
} | ||
|
||
func TestNewResourceWithCustomizedName(t *testing.T) { | ||
admin := New(&qor.Config{DB: db}) | ||
user := admin.NewResource(&UserWithCustomizedName{}) | ||
|
||
if user.Name != "CustomizedName" { | ||
t.Error("customize resource name didn't set") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package admin | ||
|
||
import "github.com/qor/qor" | ||
|
||
// Auth is an auth interface that used to qor admin | ||
// If you want to implement an authorization gateway for admin interface, you could implement this interface, and set it to the admin with `admin.SetAuth(auth)` | ||
type Auth interface { | ||
GetCurrentUser(*Context) qor.CurrentUser | ||
LoginURL(*Context) string | ||
LogoutURL(*Context) string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package admin | ||
|
||
import "github.com/qor/roles" | ||
|
||
// Config admin config struct | ||
type Config struct { | ||
Name string | ||
Menu []string | ||
Invisible bool | ||
Permission *roles.Permission | ||
Themes []string | ||
PageCount int | ||
Singleton bool | ||
} |
Oops, something went wrong.