Skip to content

Commit

Permalink
Merge pull request #14 from Crecto/master
Browse files Browse the repository at this point in the history
Sync with master
  • Loading branch information
jianghengle committed Aug 13, 2018
2 parents a7e88f0 + a90abd6 commit 185f018
Show file tree
Hide file tree
Showing 13 changed files with 142 additions and 87 deletions.
18 changes: 15 additions & 3 deletions README.md
Expand Up @@ -53,8 +53,8 @@ To modify the behaviour and display of index, form fields and search fields, the

Field types:
* `bool`: checkbox
* `int`: number input, step 1
* `float`: number input, step: any
* `int`: number input, step: "1"
* `float`: number input, step: "any" or the third item of the tuple
* `enum`: select from the options (the third item of the tuple)
* `string`: text input
* `text`: textarea
Expand Down Expand Up @@ -148,7 +148,7 @@ Permission check will only be enabled if the authentication enabled. By the defa
It defines who can create a new record by what form attributes.
* `true`: the user can create a new record
* `false`: the user cannot create any record
* `Array(Symbol | Tuple(Symbol, String) | Tuple(Symbol, String, Array(String) | String))`: the user can create a new record by the specfic form attributes
* `Array(Symbol | Tuple(Symbol, String) | Tuple(Symbol, String, Array(String) | String))`: the user can create a new record by the specified form attributes
Note that the attributes in the create form will be the intersection of the accessible attribute, the model form attribute and the specified attribute returned from this method
* edit permission
```crystal
Expand All @@ -168,6 +168,18 @@ Permission check will only be enabled if the authentication enabled. By the defa
* `false`: the user cannot delete this record
Note that, be default if this method not defined, the user can delete the record as long as he has the edit permission

## Event Hooks

There are six events that users could hook their custom event callbacks. To hook up an event, simply define a instance method on the model. The input of each method is the `user : (String | Crecto::Model)?` returned from the authentication. If the authentication is not enabled, the input is `nil`. These hooks could be used to log actions or even make some version control functions.
```crystal
def before_create(user)
def after_created(user)
def before_update(user)
def after_update(user)
def before_delete(user)
def after_deleted(user)
```

## Development

TODO: Write development instructions here
Expand Down
25 changes: 15 additions & 10 deletions examples/complete.cr
Expand Up @@ -27,7 +27,7 @@ class User < Crecto::Model
field :is_active, Bool
field :name, String
field :signature, String
field :level, Int32
field :level, Int32, default: 0
field :balance, Float64
field :last_posted, Time
end
Expand All @@ -40,14 +40,14 @@ class User < Crecto::Model
# specifiy fields and/or their types to be show in the create/update form
def self.form_attributes
[{:email, "string"},
{:encrypted_password, "password"},
:encrypted_password,
{:name, "string"},
{:signature, "text"},
{:role, "enum", ["admin", "user"]},
{:is_active, "bool"},
{:level, "int"},
{:balance, "float"},
{:last_posted, "time"}]
:is_active,
:level,
{:balance, "float", "0.01"},
:last_posted]
end

# only admin can create new user
Expand Down Expand Up @@ -101,10 +101,7 @@ class Blog < Crecto::Model
end

def self.form_attributes
[{:user_id, "int"},
{:is_public, "bool"},
{:title, "string"},
{:content, "text"}]
[:user_id, :is_public, {:title, "string"}, {:content, "text"}]
end

# admin can do anything
Expand Down Expand Up @@ -141,6 +138,14 @@ class Blog < Crecto::Model
return true if user.role.to_s == "admin"
@user_id == user.id
end

# hook up after created event
# when a user posts a blog, update the user's last posted time
def after_created(user)
return unless user.is_a? User
user.last_posted = Time.now
Repo.update(user)
end
end

# Configure global behaviors before initializing admin server
Expand Down
Binary file modified public/crecto.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions public/css/main.css
Expand Up @@ -84,3 +84,7 @@
.total-rows-button {
padding-left: 5px;
}

input[placeholder] {
text-overflow: ellipsis;
}
94 changes: 58 additions & 36 deletions src/CrectoAdmin/resource.cr
Expand Up @@ -6,32 +6,34 @@ def self.admin_resource(model : Crecto::Model.class, repo, **opts)
collection_attributes = model.responds_to?(:collection_attributes) ? model.collection_attributes : model_attributes

form_attributes = [] of Symbol | Tuple(Symbol, String) | Tuple(Symbol, String, Array(String) | String)
if model.responds_to?(:form_attributes)
form_attributes.concat(model.form_attributes)
else
model.fields.each do |f|
if CrectoAdmin.config.auth_model_password == f[:name]
form_attributes << {f[:name], "password"}
model.fields.each do |f|
if CrectoAdmin.config.auth_model_password == f[:name]
form_attributes << {f[:name], "password"}
else
attr_type = f[:type].to_s
if attr_type == "Bool"
form_attributes << {f[:name], "bool"}
elsif attr_type.starts_with?("Int")
form_attributes << {f[:name], "int"}
elsif attr_type.starts_with?("Float")
form_attributes << {f[:name], "float"}
elsif attr_type == "Time"
form_attributes << {f[:name], "time"}
else
attr_type = f[:type].to_s
if attr_type == "Bool"
form_attributes << {f[:name], "bool"}
elsif attr_type.starts_with?("Int")
form_attributes << {f[:name], "int"}
elsif attr_type.starts_with?("Float")
form_attributes << {f[:name], "float"}
elsif attr_type == "Time"
form_attributes << {f[:name], "time"}
else
form_attributes << f[:name]
end
form_attributes << f[:name]
end
end
end
if model.responds_to?(:form_attributes)
form_attributes = CrectoAdmin.merge_form_attributes(model.form_attributes, form_attributes)
end

search_attributes = model.responds_to?(:search_attributes) ? model.search_attributes : model_attributes

resource_index = CrectoAdmin.resources.size

resource = {
index: resource_index,
model: model,
repo: repo,
model_attributes: model_attributes,
Expand All @@ -42,9 +44,10 @@ def self.admin_resource(model : Crecto::Model.class, repo, **opts)
CrectoAdmin.add_resource(resource)

# Index
get "/admin/#{model.table_name}" do |ctx|
get "/admin/#{resource_index}" do |ctx|
user = CrectoAdmin.current_user(ctx)
access = CrectoAdmin.check_access(user, resource)
accesses = CrectoAdmin.check_resources(user)
access = accesses[resource_index]
next if access[0].nil? || access[1].empty?
query = access[0].as(Crecto::Repo::Query)
collection_attributes = resource[:collection_attributes].select { |a| access[1].includes? a }
Expand All @@ -64,13 +67,17 @@ def self.admin_resource(model : Crecto::Model.class, repo, **opts)
data = repo.all(model, query)
form_attributes = CrectoAdmin.check_create(user, resource, access[1])
search_param = nil
search_attributes = search_attributes.select { |a| access[1].includes? a }
search_attributes.delete(model.primary_key_field_symbol)
search_attributes.unshift(model.primary_key_field_symbol)
ecr("index")
end

# Search
get "/admin/#{model.table_name}/search" do |ctx|
get "/admin/#{resource_index}/search" do |ctx|
user = CrectoAdmin.current_user(ctx)
access = CrectoAdmin.check_access(user, resource)
accesses = CrectoAdmin.check_resources(user)
access = accesses[resource_index]
next if access[0].nil? || access[1].empty?
query = access[0].as(Crecto::Repo::Query)
collection_attributes = resource[:collection_attributes].select { |a| access[1].includes? a }
Expand Down Expand Up @@ -100,19 +107,21 @@ def self.admin_resource(model : Crecto::Model.class, repo, **opts)
end

# New form
get "/admin/#{model.table_name}/new" do |ctx|
get "/admin/#{resource_index}/new" do |ctx|
user = CrectoAdmin.current_user(ctx)
access = CrectoAdmin.check_access(user, resource)
accesses = CrectoAdmin.check_resources(user)
access = accesses[resource_index]
next if access[0].nil? || access[1].empty?
item = model.new
form_attributes = CrectoAdmin.check_create(user, resource, access[1])
ecr("new")
end

# View
get "/admin/#{model.table_name}/:id" do |ctx|
get "/admin/#{resource_index}/:id" do |ctx|
user = CrectoAdmin.current_user(ctx)
access = CrectoAdmin.check_access(user, resource)
accesses = CrectoAdmin.check_resources(user)
access = accesses[resource_index]
next if access[0].nil? || access[1].empty?
query = access[0].as(Crecto::Repo::Query)
query = query.where(resource[:model].primary_key_field_symbol, ctx.params.url["id"])
Expand All @@ -126,9 +135,10 @@ def self.admin_resource(model : Crecto::Model.class, repo, **opts)
end

# Update
put "/admin/#{model.table_name}/:pid_id" do |ctx|
put "/admin/#{resource_index}/:pid_id" do |ctx|
user = CrectoAdmin.current_user(ctx)
access = CrectoAdmin.check_access(user, resource)
accesses = CrectoAdmin.check_resources(user)
access = accesses[resource_index]
next if access[0].nil? || access[1].empty?
item = repo.get!(model, ctx.params.url["pid_id"])
form_attributes = CrectoAdmin.check_edit(user, resource, item, access[1])
Expand All @@ -149,21 +159,24 @@ def self.admin_resource(model : Crecto::Model.class, repo, **opts)
end
end
item.update_from_hash(query_hash)
item.before_update(user) if item.responds_to? :before_update
changeset = repo.update(item)

if changeset.errors.any?
ctx.flash["error"] = CrectoAdmin.changeset_errors(changeset)
ecr("edit")
else
item.after_updated(user) if item.responds_to? :after_updated
ctx.flash["success"] = "Updated successfully"
ctx.redirect "/admin/#{model.table_name}/#{item.pkey_value}"
ctx.redirect "/admin/#{resource_index}/#{item.pkey_value}"
end
end

# Edit form
get "/admin/#{model.table_name}/:id/edit" do |ctx|
get "/admin/#{resource_index}/:id/edit" do |ctx|
user = CrectoAdmin.current_user(ctx)
access = CrectoAdmin.check_access(user, resource)
accesses = CrectoAdmin.check_resources(user)
access = accesses[resource_index]
next if access[0].nil? || access[1].empty?
query = access[0].as(Crecto::Repo::Query)
query = query.where(resource[:model].primary_key_field_symbol, ctx.params.url["id"])
Expand All @@ -175,7 +188,11 @@ def self.admin_resource(model : Crecto::Model.class, repo, **opts)
end

# Create
post "/admin/#{model.table_name}" do |ctx|
post "/admin/#{resource_index}" do |ctx|
user = CrectoAdmin.current_user(ctx)
accesses = CrectoAdmin.check_resources(user)
access = accesses[resource_index]
next if access[0].nil? || access[1].empty?
item = model.new
query_hash = ctx.params.body.to_h
resource[:form_attributes].each do |attr|
Expand All @@ -194,34 +211,39 @@ def self.admin_resource(model : Crecto::Model.class, repo, **opts)
end
end
item.update_from_hash(query_hash)
item.before_create(user) if item.responds_to? :before_create
changeset = repo.insert(item)

if changeset.errors.any?
ctx.flash["error"] = CrectoAdmin.changeset_errors(changeset)
ecr("new")
else
item.after_created(user) if item.responds_to? :after_created
ctx.flash["success"] = "Created sucessfully"
ctx.redirect "/admin/#{model.table_name}/#{changeset.instance.pkey_value}"
ctx.redirect "/admin/#{resource_index}/#{changeset.instance.pkey_value}"
end
end

# Delete
get "/admin/#{model.table_name}/:id/delete" do |ctx|
get "/admin/#{resource_index}/:id/delete" do |ctx|
user = CrectoAdmin.current_user(ctx)
access = CrectoAdmin.check_access(user, resource)
accesses = CrectoAdmin.check_resources(user)
access = accesses[resource_index]
next if access[0].nil? || access[1].empty?
item = repo.get!(model, ctx.params.url["id"])
form_attributes = CrectoAdmin.check_edit(user, resource, item, access[1])
can_delete = CrectoAdmin.check_delete(user, resource, item, form_attributes)
next unless can_delete
item.before_delete(user) if item.responds_to? :before_delete
changeset = repo.delete(item)
item.after_deleted(user) if item.responds_to? :after_deleted

if changeset.errors.any?
ctx.flash["error"] = CrectoAdmin.changeset_errors(changeset)
ecr("show")
else
ctx.flash["success"] = "Deleted successfully"
ctx.redirect "/admin/#{model.table_name}"
ctx.redirect "/admin/#{resource_index}"
end
end
end
6 changes: 6 additions & 0 deletions src/CrectoAdmin/views/_form_fields.ecr
Expand Up @@ -92,6 +92,12 @@
<div class="control">
<input class="input is-static" readonly name="<%= attr_name %>" id="<%= attr_name %>" type="text" value="<%= query_hash[attr_name] ? query_hash[attr_name] : attr.last%>">
</div>
<% elsif attr_type == "float" && last.is_a?(String) %>
<% last = last.as(String) %>
<label class="label"><%= attr_name %></label>
<div class="control">
<input class="input" placeholder="<%= attr_name %>" name="<%= attr_name %>" id="<%= attr_name %>" type="number" step="<%= last %>" value="<%= query_hash[attr_name] %>">
</div>
<% end %>
<% end %>
</div>
Expand Down
11 changes: 7 additions & 4 deletions src/CrectoAdmin/views/admin_layout.ecr
Expand Up @@ -64,14 +64,17 @@
<% else %>
<li><a href="/admin/dashboard">Dashboard</a></li>
<% end %>
<% CrectoAdmin.accessible_resources(ctx).each do |resource| %>
<% CrectoAdmin.resources.each do |resource| %>
<% access = accesses[resource[:index]] %>
<% next if access[0].nil? %>
<% next if access[1].empty? %>
<li>
<% if CrectoAdmin.current_table(ctx) == resource[:model].table_name %>
<a class="is-active" href="/admin/<%= resource[:model].table_name %>">
<% if CrectoAdmin.current_table(ctx) == resource[:index].to_s %>
<a class="is-active" href="/admin/<%= resource[:index] %>">
<%= resource[:model].table_name.split('_').map(&.capitalize).join(' ') %>
</a>
<% else %>
<a href="/admin/<%= resource[:model].table_name %>">
<a href="/admin/<%= resource[:index] %>">
<%= resource[:model].table_name.split('_').map(&.capitalize).join(' ') %>
</a>
<% end %>
Expand Down
9 changes: 6 additions & 3 deletions src/CrectoAdmin/views/dashboard.ecr
Expand Up @@ -13,11 +13,14 @@
</thead>

<tbody>
<% CrectoAdmin.accessible_resources(ctx).each_with_index do |resource, i| %>
<tr class="item-row" data-href="/admin/<%= resource[:model].table_name %>">
<% CrectoAdmin.resources.each do |resource| %>
<% access = accesses[resource[:index]] %>
<% next if access[0].nil? %>
<% next if access[1].empty? %>
<tr class="item-row" data-href="/admin/<%= resource[:index] %>">
<th><%= resource[:model].table_name.split('_').map(&.capitalize).join(' ') %></th>
<td><%= resource[:model_attributes].map(&.to_s).join(", ") %></td>
<td><%= counts[i] %></td>
<td><%= counts[resource[:index]] %></td>
</tr>
<% end %>
</tbody>
Expand Down
2 changes: 1 addition & 1 deletion src/CrectoAdmin/views/edit.ecr
Expand Up @@ -11,7 +11,7 @@
</div>

<div>
<form method="post" action="/admin/<%= item.class.table_name %>/<%= item.pkey_value %>">
<form method="post" action="/admin/<%= resource[:index] %>/<%= item.pkey_value %>">
<input type="hidden" name="authenticity_token" value="<%= ctx.session.string("csrf") %>" />
<input type="hidden" name="_method" value="put" />
<%= ecr "_form_fields" %>
Expand Down

0 comments on commit 185f018

Please sign in to comment.