rails_admin_import_no_encoding 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +48 -0
- data/MIT-LICENSE +20 -0
- data/README.md +476 -0
- data/Rakefile +17 -0
- data/app/views/rails_admin/main/_results.html.haml +18 -0
- data/app/views/rails_admin/main/_section.html.haml +10 -0
- data/app/views/rails_admin/main/import.html.haml +69 -0
- data/config/locales/README.md +3 -0
- data/config/locales/import.en.yml +50 -0
- data/lib/rails_admin_import/action.rb +46 -0
- data/lib/rails_admin_import/config/legacy_model.rb +42 -0
- data/lib/rails_admin_import/config/sections/import.rb +34 -0
- data/lib/rails_admin_import/config.rb +46 -0
- data/lib/rails_admin_import/eager_load.rb +3 -0
- data/lib/rails_admin_import/engine.rb +4 -0
- data/lib/rails_admin_import/formats/csv_importer.rb +81 -0
- data/lib/rails_admin_import/formats/dummy_importer.rb +16 -0
- data/lib/rails_admin_import/formats/file_importer.rb +47 -0
- data/lib/rails_admin_import/formats/json_importer.rb +32 -0
- data/lib/rails_admin_import/formats/xlsx_importer.rb +50 -0
- data/lib/rails_admin_import/formats.rb +34 -0
- data/lib/rails_admin_import/import_logger.rb +17 -0
- data/lib/rails_admin_import/import_model.rb +112 -0
- data/lib/rails_admin_import/importer.rb +248 -0
- data/lib/rails_admin_import/rails_admin_plugin.rb +23 -0
- data/lib/rails_admin_import/version.rb +3 -0
- data/lib/rails_admin_import.rb +23 -0
- metadata +115 -0
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
|
3
|
+
require 'rubygems/tasks'
|
4
|
+
Gem::Tasks.new
|
5
|
+
|
6
|
+
# Piggyback off the Rails Admin rake tasks to set up the CI environment
|
7
|
+
spec = Gem::Specification.find_by_name 'rails_admin'
|
8
|
+
Dir["#{spec.gem_dir}/lib/tasks/*.rake"].each { |rake| load rake }
|
9
|
+
|
10
|
+
require 'rspec/core/rake_task'
|
11
|
+
RSpec::Core::RakeTask.new(:spec)
|
12
|
+
|
13
|
+
task test: :spec
|
14
|
+
|
15
|
+
task :default do
|
16
|
+
system("bundle exec rake spec")
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
- if @results && @results[:success].any?
|
2
|
+
.alert.alert-success.form-horizontal.denser
|
3
|
+
%fieldset
|
4
|
+
%legend
|
5
|
+
%i.icon-chevron-right
|
6
|
+
= @results[:success_message]
|
7
|
+
%ul.control-group{style: 'display: none'}
|
8
|
+
- @results[:success].each do |message|
|
9
|
+
%li= message
|
10
|
+
- if @results && @results[:error].any?
|
11
|
+
.alert.alert-danger.form-horizontal.denser
|
12
|
+
%fieldset
|
13
|
+
%legend
|
14
|
+
%i.icon-chevron-down
|
15
|
+
= @results[:error_message]
|
16
|
+
%ul.control-group
|
17
|
+
- @results[:error].each do |message|
|
18
|
+
%li= message
|
@@ -0,0 +1,10 @@
|
|
1
|
+
- if fields.any?
|
2
|
+
.form-group.control-group
|
3
|
+
%label.col-sm-2.control-label= t("admin.import.#{section}")
|
4
|
+
.col-sm-10.controls
|
5
|
+
%ul.list-unstyled
|
6
|
+
- fields.each do |field|
|
7
|
+
%li
|
8
|
+
%label= capitalize_first_letter(field.label)
|
9
|
+
%p.help-block= t("admin.import.help.#{section}")
|
10
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
= render "results"
|
2
|
+
|
3
|
+
= form_tag import_path(@abstract_model), :multipart => true, class: 'form-horizontal denser' do
|
4
|
+
|
5
|
+
%input{name: "send_data", type: "hidden", value: "true"}/
|
6
|
+
%fieldset
|
7
|
+
%legend
|
8
|
+
%i.icon-chevron-down
|
9
|
+
= t('admin.import.legend.fields')
|
10
|
+
|
11
|
+
= render "section", section: "model_fields", fields: @import_model.model_fields
|
12
|
+
= render "section", section: "association_fields", fields: @import_model.association_fields
|
13
|
+
|
14
|
+
%fieldset
|
15
|
+
%legend
|
16
|
+
%i.icon-chevron-down
|
17
|
+
= t('admin.import.legend.upload')
|
18
|
+
.form-group.control-group
|
19
|
+
%label.col-sm-2.control-label{for: "file"}= t("admin.import.file")
|
20
|
+
.col-sm-10.controls
|
21
|
+
= file_field_tag :file, :class => "form-control"
|
22
|
+
%p.help-block= t('admin.import.help.file_limit', limit: RailsAdminImport.config.line_item_limit)
|
23
|
+
.form-group.control-group
|
24
|
+
%label.col-sm-2.control-label{for: "encoding"}= t("admin.import.encoding")
|
25
|
+
.col-sm-10.controls
|
26
|
+
= select_tag 'encoding',
|
27
|
+
options_for_select(Encoding.name_list.sort),
|
28
|
+
include_blank: true, data: { enumeration: true }
|
29
|
+
%p.help-block= t('admin.import.help.encoding', name: 'UTF-8')
|
30
|
+
.form-group.control-group
|
31
|
+
%label.col-sm-2.control-label= t("admin.import.update_if_exists")
|
32
|
+
.col-sm-10.controls
|
33
|
+
= check_box_tag :update_if_exists, '1', RailsAdminImport.config.update_if_exists, :class => "form-control"
|
34
|
+
%p.help-block= t('admin.import.help.update_if_exists')
|
35
|
+
.form-group.control-group
|
36
|
+
%label.col-sm-2.control-label{for: "update_lookup"}= t("admin.import.update_lookup")
|
37
|
+
.col-sm-10.controls
|
38
|
+
= select_tag 'update_lookup',
|
39
|
+
options_for_select(@import_model.update_lookup_field_names,
|
40
|
+
Array.wrap(@import_model.config.mapping_key).map(&:to_s)),
|
41
|
+
multiple: true,
|
42
|
+
data: { enumeration: true }
|
43
|
+
|
44
|
+
- unless @import_model.association_fields.empty?
|
45
|
+
%fieldset
|
46
|
+
%legend
|
47
|
+
%i.icon-chevron-down
|
48
|
+
= t('admin.import.legend.mapping')
|
49
|
+
|
50
|
+
- @import_model.association_fields.each do |field|
|
51
|
+
.form-group.control-group
|
52
|
+
%label.col-sm-2.control-label
|
53
|
+
= capitalize_first_letter(field.label)
|
54
|
+
= t("admin.import.mapping")
|
55
|
+
.col-sm-10.controls
|
56
|
+
= select_tag "associations[#{field.name}]",
|
57
|
+
options_for_select(@import_model.associated_model_fields(field),
|
58
|
+
Array.wrap(@import_model.associated_config(field).mapping_key).first.to_s),
|
59
|
+
data: { enumeration: true }
|
60
|
+
|
61
|
+
%br
|
62
|
+
.form-actions
|
63
|
+
%input{type: :hidden, name: 'return_to', value: (request.params[:return_to].presence || request.referer)}
|
64
|
+
%button.btn.btn-primary{type: "submit", name: "commit", data: {disable_with: "Uploading..."} }
|
65
|
+
%i.icon-white.icon-ok
|
66
|
+
= t("admin.form.save")
|
67
|
+
%button.btn{type: "submit", name: "_continue"}
|
68
|
+
%i.icon-remove
|
69
|
+
= t("admin.form.cancel")
|
@@ -0,0 +1,3 @@
|
|
1
|
+
See the [community contributed translation section of the README](https://github.com/stephskardal/rails_admin_import#community-contributed-translations) for more languages.
|
2
|
+
|
3
|
+
To contribute a new translation, please put it in a gist and submit a pull request to link your translation in the README.
|
@@ -0,0 +1,50 @@
|
|
1
|
+
|
2
|
+
en:
|
3
|
+
admin:
|
4
|
+
actions:
|
5
|
+
import:
|
6
|
+
title: "Import"
|
7
|
+
menu: "Import"
|
8
|
+
breadcrumb: "Import"
|
9
|
+
link: "Import"
|
10
|
+
bulk_link: "Import"
|
11
|
+
done: "Imported"
|
12
|
+
import:
|
13
|
+
model_fields: "Model fields"
|
14
|
+
association_fields: "Association fields"
|
15
|
+
|
16
|
+
file: "Data file"
|
17
|
+
missing_file: "You must select a file"
|
18
|
+
format: "File format"
|
19
|
+
invalid_format: "Invalid import format."
|
20
|
+
missing_update_lookup: "Your file must contain a column for the 'Update lookup field' you selected."
|
21
|
+
invalid_json: "The JSON data should be an array of records or an object with a key '%{root_key}' set to an array of records"
|
22
|
+
update_if_exists: "Update if exists"
|
23
|
+
update_lookup: "Update lookup field(s)"
|
24
|
+
mapping: "mapping"
|
25
|
+
encoding: "Encoding"
|
26
|
+
legend:
|
27
|
+
fields: "Fields to import"
|
28
|
+
upload: "Upload file"
|
29
|
+
mapping: "Related fields mapping"
|
30
|
+
import_success:
|
31
|
+
create: "Created %{name}"
|
32
|
+
update: "Updated %{name}"
|
33
|
+
import_error:
|
34
|
+
create: "Failed to create %{name}: %{error}"
|
35
|
+
update: "Failed to update %{name}: %{error}"
|
36
|
+
general: "Error during import: %{error}"
|
37
|
+
line_item_limit: "Please limit upload file to %{limit} line items."
|
38
|
+
old_import_hook: >
|
39
|
+
The import hook %{model}.%{method} should take only 1 argument.
|
40
|
+
Data may not imported correctly.
|
41
|
+
See Upgrading section readme in Rails Admin Import.
|
42
|
+
association_not_found: "Association not found. %{error}"
|
43
|
+
help:
|
44
|
+
model_fields: "The fields above may be included in the import file."
|
45
|
+
association_fields: >
|
46
|
+
These fields map to other tables in the database, lookup via attribute selected below.
|
47
|
+
For "many" associations, you may include multiple columns with the same header in the CSV file.
|
48
|
+
update_if_exists: "Update records found with the lookup field below instead of creating new records"
|
49
|
+
file_limit: "Please limit upload file to %{limit} line items."
|
50
|
+
encoding: "Choose file encoding. Leave empty to auto-detect. Ignored for JSON."
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "rails_admin/config/actions"
|
2
|
+
|
3
|
+
module RailsAdmin
|
4
|
+
module Config
|
5
|
+
module Actions
|
6
|
+
class Import < Base
|
7
|
+
RailsAdmin::Config::Actions.register(self)
|
8
|
+
|
9
|
+
register_instance_option :collection do
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
register_instance_option :http_methods do
|
14
|
+
[:get, :post]
|
15
|
+
end
|
16
|
+
|
17
|
+
register_instance_option :controller do
|
18
|
+
proc do
|
19
|
+
@import_model = RailsAdminImport::ImportModel.new(@abstract_model)
|
20
|
+
|
21
|
+
if request.post?
|
22
|
+
format = RailsAdminImport::Formats.from_file(params[:file])
|
23
|
+
record_importer = RailsAdminImport::Formats.for(
|
24
|
+
format, @import_model, params)
|
25
|
+
|
26
|
+
if record_importer.valid?
|
27
|
+
importer = RailsAdminImport::Importer.new(
|
28
|
+
@import_model, params)
|
29
|
+
|
30
|
+
@results = importer.import(record_importer.each)
|
31
|
+
else
|
32
|
+
flash[:error] = record_importer.error
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
render action: @action.template_name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
register_instance_option :link_icon do
|
41
|
+
"icon-folder-open"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RailsAdminImport
|
2
|
+
module Config
|
3
|
+
class LegacyModel
|
4
|
+
attr_reader :model_name
|
5
|
+
def initialize(model_name)
|
6
|
+
@model_name = model_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def label(_value)
|
10
|
+
# Ignored now
|
11
|
+
# RailsAdmin object_label_method will be used
|
12
|
+
end
|
13
|
+
|
14
|
+
def mapping_key(value)
|
15
|
+
config = RailsAdmin.config(model_name)
|
16
|
+
config.mapping_key(value)
|
17
|
+
end
|
18
|
+
|
19
|
+
def excluded_fields(values)
|
20
|
+
config = RailsAdmin.config(model_name)
|
21
|
+
|
22
|
+
# Call appropriate Rails Admin field list methods
|
23
|
+
config.import do
|
24
|
+
include_all_fields
|
25
|
+
exclude_fields *values
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def extra_fields(values)
|
30
|
+
config = RailsAdmin.config(model_name)
|
31
|
+
|
32
|
+
# Call appropriate Rails Admin field list methods
|
33
|
+
config.import do
|
34
|
+
include_all_fields
|
35
|
+
values.each do |value|
|
36
|
+
field value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "rails_admin/config/sections/base"
|
2
|
+
|
3
|
+
module RailsAdmin
|
4
|
+
module Config
|
5
|
+
module Sections
|
6
|
+
# Configuration of the navigation view
|
7
|
+
class Import < RailsAdmin::Config::Sections::Base
|
8
|
+
register_instance_option(:mapping_key) do
|
9
|
+
:name
|
10
|
+
end
|
11
|
+
|
12
|
+
register_instance_option(:mapping_key_list) do
|
13
|
+
[]
|
14
|
+
end
|
15
|
+
|
16
|
+
register_instance_option(:default_excluded_fields) do
|
17
|
+
[:id, :_id, :created_at, :updated_at, :c_at, :u_at, :deleted_at]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
section = RailsAdmin::Config::Sections::Import
|
25
|
+
name = :import
|
26
|
+
|
27
|
+
# Manually add to Rails Admin as a model configuration section until
|
28
|
+
# there is a better API to do this
|
29
|
+
RailsAdmin::Config::Model.send(:define_method, name) do |&block|
|
30
|
+
@sections = {} unless @sections
|
31
|
+
@sections[name] = section.new(self) unless @sections[name]
|
32
|
+
@sections[name].instance_eval(&block) if block
|
33
|
+
@sections[name]
|
34
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "rails_admin_import/config/legacy_model"
|
2
|
+
|
3
|
+
module RailsAdminImport
|
4
|
+
module Config
|
5
|
+
class << self
|
6
|
+
attr_accessor :logging
|
7
|
+
attr_accessor :line_item_limit
|
8
|
+
attr_accessor :rollback_on_error
|
9
|
+
attr_accessor :update_if_exists
|
10
|
+
attr_accessor :header_converter
|
11
|
+
attr_accessor :csv_options
|
12
|
+
|
13
|
+
# Default is to downcase headers and add underscores to convert into attribute names
|
14
|
+
HEADER_CONVERTER = lambda do |header|
|
15
|
+
# check for nil/blank headers
|
16
|
+
next if header.blank?
|
17
|
+
header.parameterize.underscore
|
18
|
+
end
|
19
|
+
|
20
|
+
def model(model_name, &block)
|
21
|
+
unless @deprecation_shown
|
22
|
+
warn "RailsAdminImport::Config#model is deprecated. " \
|
23
|
+
"Add a import section for your model inside the rails_admin " \
|
24
|
+
"config block. See the Readme.md for more details"
|
25
|
+
@deprecation_shown = true
|
26
|
+
end
|
27
|
+
legacy_config = RailsAdminImport::Config::LegacyModel.new(model_name)
|
28
|
+
legacy_config.instance_eval(&block) if block
|
29
|
+
legacy_config
|
30
|
+
end
|
31
|
+
|
32
|
+
# Reset all configurations to defaults.
|
33
|
+
def reset
|
34
|
+
@logging = false
|
35
|
+
@line_item_limit = 1000
|
36
|
+
@rollback_on_error = false
|
37
|
+
@update_if_exists = false
|
38
|
+
@header_converter = HEADER_CONVERTER
|
39
|
+
@csv_options = {}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Set default values for configuration options on load
|
44
|
+
self.reset
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "csv"
|
2
|
+
|
3
|
+
module RailsAdminImport
|
4
|
+
module Formats
|
5
|
+
class CSVImporter < FileImporter
|
6
|
+
Formats.register(:csv, self)
|
7
|
+
Formats.register(:CSV, self)
|
8
|
+
|
9
|
+
autoload :CharDet, "rchardet"
|
10
|
+
|
11
|
+
def initialize(import_model, params)
|
12
|
+
super
|
13
|
+
@encoding = params[:encoding]
|
14
|
+
@header_converter = RailsAdminImport.config.header_converter
|
15
|
+
end
|
16
|
+
|
17
|
+
# A method that yields a hash of attributes for each record to import
|
18
|
+
def each_record
|
19
|
+
CSV.foreach(filename, csv_options) do |row|
|
20
|
+
attr = convert_to_attributes(row)
|
21
|
+
yield attr unless attr.all? { |field, value| value.blank? }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def csv_options
|
28
|
+
defaults = RailsAdminImport.config.csv_options
|
29
|
+
options = {
|
30
|
+
headers: true,
|
31
|
+
header_converters: @header_converter,
|
32
|
+
encoding: encoding,
|
33
|
+
}
|
34
|
+
|
35
|
+
defaults.merge(options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def encoding
|
39
|
+
from_encoding =
|
40
|
+
if !@encoding.blank?
|
41
|
+
@encoding
|
42
|
+
else
|
43
|
+
detect_encoding
|
44
|
+
end
|
45
|
+
|
46
|
+
to_encoding = import_model.abstract_model.encoding
|
47
|
+
|
48
|
+
if from_encoding && from_encoding != to_encoding
|
49
|
+
"#{from_encoding}:#{to_encoding}"
|
50
|
+
else
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def detect_encoding
|
56
|
+
# Su: Remove CharlockHolmes since it require to another software http://site.icu-project.org/home
|
57
|
+
# which is not available on server
|
58
|
+
# charset = CharlockHolmes::EncodingDetector.detect File.read(filename)
|
59
|
+
# if charset[:confidence] > 0.6
|
60
|
+
# from_encoding = charset[:encoding]
|
61
|
+
# from_encoding = "UTF-8" if from_encoding == "ascii"
|
62
|
+
# end
|
63
|
+
from_encoding = "UTF-8"
|
64
|
+
from_encoding
|
65
|
+
end
|
66
|
+
|
67
|
+
def convert_to_attributes(row)
|
68
|
+
row.each_with_object({}) do |(field, value), record|
|
69
|
+
next if field.blank?
|
70
|
+
field = field.to_sym
|
71
|
+
if import_model.has_multiple_values?(field)
|
72
|
+
field = import_model.pluralize_field(field)
|
73
|
+
(record[field] ||= []) << value
|
74
|
+
else
|
75
|
+
record[field] = value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|