solidus_import_products 2.0.1
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/LICENSE +19 -0
- data/README.md +71 -0
- data/app/controllers/spree/admin/product_imports_controller.rb +35 -0
- data/app/jobs/import_products_job.rb +14 -0
- data/app/models/solidus_import_products/parser.rb +15 -0
- data/app/models/solidus_import_products/parser/base.rb +24 -0
- data/app/models/solidus_import_products/parser/csv.rb +98 -0
- data/app/models/spree/product_import.rb +82 -0
- data/app/overrides/add_import_to_admin_sidebar_menu.rb +6 -0
- data/app/services/solidus_import_products/create_variant.rb +109 -0
- data/app/services/solidus_import_products/import.rb +38 -0
- data/app/services/solidus_import_products/process_row.rb +93 -0
- data/app/services/solidus_import_products/save_product.rb +41 -0
- data/app/services/solidus_import_products/save_properties.rb +23 -0
- data/app/services/solidus_import_products/update_product.rb +45 -0
- data/app/views/spree/admin/product_imports/index.html.erb +71 -0
- data/app/views/spree/admin/product_imports/show.html.erb +37 -0
- data/app/views/spree/admin/shared/_import_sidebar_menu.erb +3 -0
- data/app/views/spree/user_mailer/product_import_results.text.erb +23 -0
- data/lib/generators/solidus_import_products/install/install_generator.rb +41 -0
- data/lib/generators/solidus_import_products/install/templates/config/initializers/solidus_import_product_settings.rb +10 -0
- data/lib/solidus_import_products.rb +8 -0
- data/lib/solidus_import_products/engine.rb +26 -0
- data/lib/solidus_import_products/exception.rb +19 -0
- data/lib/solidus_import_products/import_helper.rb +118 -0
- data/lib/solidus_import_products/logger.rb +15 -0
- data/lib/solidus_import_products/user_mailer_ext.rb +15 -0
- data/lib/tasks/solidus_import_products.rake +1 -0
- metadata +255 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module SolidusImportProducts
|
2
|
+
class Engine < Rails::Engine
|
3
|
+
engine_name 'import_products'
|
4
|
+
|
5
|
+
config.autoload_paths += %W[#{config.root}/lib]
|
6
|
+
|
7
|
+
def self.activate
|
8
|
+
Dir.glob(File.join(File.dirname(__FILE__), '../app/**/*_decorator*.rb')) do |c|
|
9
|
+
Rails.env.production? ? require(c) : load(c)
|
10
|
+
end
|
11
|
+
|
12
|
+
Dir.glob(File.join(File.dirname(__FILE__), '../../app/overrides/*.rb')) do |c|
|
13
|
+
Rails.application.config.cache_classes ? require(c) : load(c)
|
14
|
+
end
|
15
|
+
|
16
|
+
Spree::UserMailer.send(:include, SolidusImportProducts::UserMailerExt)
|
17
|
+
|
18
|
+
# TODO: replace the class_eval
|
19
|
+
Spree::User.class_eval do
|
20
|
+
has_many :product_imports, class_name: 'Spree::ProductImport', foreign_key: 'created_by'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
config.to_prepare &method(:activate).to_proc
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SolidusImportProducts
|
2
|
+
module Exception
|
3
|
+
class Base < StandardError; end
|
4
|
+
|
5
|
+
class ProductError < Base; end
|
6
|
+
class VariantError < Base; end
|
7
|
+
class ImportError < Base; end
|
8
|
+
class SkuError < Base; end
|
9
|
+
class InvalidPrice < Base; end
|
10
|
+
|
11
|
+
class AbstractMthodCall < Base
|
12
|
+
def initialize(msg = 'This methid should be implemented in the subclass')
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class InvalidParseStrategy < Base; end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module SolidusImportProducts
|
4
|
+
module ImportHelper
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
require 'open-uri'
|
8
|
+
|
9
|
+
included do
|
10
|
+
### IMAGE HELPERS ###
|
11
|
+
# find_and_attach_image_to
|
12
|
+
# This method attaches images to products. The images may come
|
13
|
+
# from a local source (i.e. on disk), or they may be online (HTTP/HTTPS).
|
14
|
+
def find_and_attach_image_to(product_or_variant, filename)
|
15
|
+
return if filename.blank?
|
16
|
+
|
17
|
+
temp_file = fetch_image(filename)
|
18
|
+
return unless temp_file
|
19
|
+
product_image = Spree::Image.new(attachment: temp_file,
|
20
|
+
viewable_id: product_or_variant.id,
|
21
|
+
viewable_type: 'Spree::Variant',
|
22
|
+
alt: '',
|
23
|
+
position: product_or_variant.images.length)
|
24
|
+
|
25
|
+
logger.log("#{product_image.viewable_id} : #{product_image.viewable_type} : #{product_image.position}", :debug)
|
26
|
+
|
27
|
+
product_or_variant.images << product_image if product_image.save
|
28
|
+
end
|
29
|
+
|
30
|
+
def fetch_image(filename)
|
31
|
+
filename =~ /\Ahttp[s]*:\/\// ? fetch_remote_image(filename) : fetch_local_image(filename)
|
32
|
+
end
|
33
|
+
|
34
|
+
# This method is used when we have a set location on disk for
|
35
|
+
# images, and the file is accessible to the script.
|
36
|
+
# It is basically just a wrapper around basic File IO methods.
|
37
|
+
def fetch_local_image(filename)
|
38
|
+
filename = Spree::ProductImport.settings[:product_image_path] + filename
|
39
|
+
unless File.exist?(filename) && File.readable?(filename)
|
40
|
+
logger.log("Image #{filename} was not found on the server, so this image was not imported.", :warn)
|
41
|
+
return nil
|
42
|
+
end
|
43
|
+
File.open(filename, 'rb')
|
44
|
+
end
|
45
|
+
|
46
|
+
# This method can be used when the filename matches the format of a URL.
|
47
|
+
# It uses open-uri to fetch the file, returning a Tempfile object if it
|
48
|
+
# is successful.
|
49
|
+
# If it fails, it in the first instance logger.logs the HTTP error (404, 500 etc)
|
50
|
+
# If it fails altogether, it logger.logs it and exits the method.
|
51
|
+
def fetch_remote_image(filename)
|
52
|
+
io = open(URI.parse(filename))
|
53
|
+
def io.original_filename
|
54
|
+
base_uri.path.split('/').last
|
55
|
+
end
|
56
|
+
return io
|
57
|
+
rescue OpenURI::HTTPError => error
|
58
|
+
logger.log("Image #{filename} retrival returned #{error.message}, so this image was not imported")
|
59
|
+
return nil
|
60
|
+
end
|
61
|
+
|
62
|
+
### TAXON HELPERS ###
|
63
|
+
|
64
|
+
# associate_product_with_taxon
|
65
|
+
# This method accepts three formats of taxon hierarchy strings which will
|
66
|
+
# associate the given products with taxons:
|
67
|
+
# 1. A string on it's own will will just find or create the taxon and
|
68
|
+
# add the product to it. e.g. taxonomy = "Category", taxon_hierarchy = "Tools" will
|
69
|
+
# add the product to the 'Tools' category.
|
70
|
+
# 2. A item > item > item structured string will read this like a tree - allowing
|
71
|
+
# a particular taxon to be picked out
|
72
|
+
# 3. An item > item & item > item will work as above, but will associate multiple
|
73
|
+
# taxons with that product. This form should also work with format 1.
|
74
|
+
def associate_product_with_taxon(product, taxon_hierarchy, putInTop)
|
75
|
+
return if product.nil? || taxon_hierarchy.nil?
|
76
|
+
|
77
|
+
taxon_hierarchy.split(/\s*\|\s*/).each do |hierarchy|
|
78
|
+
hierarchy = hierarchy.split(/\s*>\s*/)
|
79
|
+
taxonomy = Spree::Taxonomy.where('lower(spree_taxonomies.name) = ?', hierarchy.first.downcase).first
|
80
|
+
|
81
|
+
if taxonomy.nil? && Spree::ProductImport.settings[:create_missing_taxonomies]
|
82
|
+
taxonomy = Spree::Taxonomy.create(name: hierarchy.first.capitalize)
|
83
|
+
end
|
84
|
+
|
85
|
+
unless taxonomy.valid?
|
86
|
+
logger.log(msg = "A product could not be imported - here is the information we have:\n" \
|
87
|
+
"Product: #{product.inspect}, Taxonomy: #{taxon_hierarchy}, Errors: #{taxonomy.errors.full_messages.join(', ')} \n", :error)
|
88
|
+
raise ProductError, msg
|
89
|
+
end
|
90
|
+
|
91
|
+
last_taxon = taxonomy.root
|
92
|
+
|
93
|
+
hierarchy.shift
|
94
|
+
hierarchy.each do |taxon|
|
95
|
+
last_taxon = last_taxon.children.find_or_create_by(name: taxon, taxonomy_id: taxonomy.id)
|
96
|
+
next if last_taxon.valid?
|
97
|
+
logger.log(msg = "A product could not be imported - here is the information we have:\n" \
|
98
|
+
"Product: #{product.inspect}, Taxonomy: #{taxonomy.inspect}, Taxon: #{last_taxon.inspect}, #{last_taxon.errors.full_messages.join(', ')}", :error)
|
99
|
+
raise ProductError, msg
|
100
|
+
end
|
101
|
+
# Spree only needs to know the most detailed taxonomy item
|
102
|
+
product.taxons << last_taxon unless product.taxons.include?(last_taxon)
|
103
|
+
end
|
104
|
+
|
105
|
+
# TODO: Not sure what this does.
|
106
|
+
if putInTop && defined?(SolidusSortProductsTaxon)
|
107
|
+
if SolidusSortProductsTaxon::Config.activated
|
108
|
+
unless product.put_in_taxons_top(product.taxons)
|
109
|
+
logger.log(msg = "A product could not be imported - here is the information we have:\n" \
|
110
|
+
"Product: #{product.inspect}, Taxons: #{product.taxons}, #{product.errors.full_messages.join(', ')}", :error)
|
111
|
+
raise SolidusImportProducts::Exception::ProductError, msg
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SolidusImportProducts
|
2
|
+
class Logger
|
3
|
+
include Singleton
|
4
|
+
|
5
|
+
attr_accessor :logger
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
self.logger = ActiveSupport::Logger.new(Spree::ProductImport.settings[:log_to])
|
9
|
+
end
|
10
|
+
|
11
|
+
def log(message, severity = :info)
|
12
|
+
logger.send severity, "[#{Time.now.to_s(:db)}] [#{severity.to_s.capitalize}] #{message}\n"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SolidusImportProducts
|
2
|
+
module UserMailerExt
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
def product_import_results(user, error_message = nil)
|
6
|
+
@user = user
|
7
|
+
@error_message = error_message
|
8
|
+
store = Spree::Store.default
|
9
|
+
# attachments["import_products.log"] = File.read(Spree::ProductImport.settings[:log_to]) if @error_message.nil?
|
10
|
+
mail(to: @user.email, from: from_address(store), subject: "Spree: Import Products #{error_message.nil? ? 'Success' : 'Failure'}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
# add custom rake tasks here
|
metadata
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: solidus_import_products
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ngelX
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-05-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: deface
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: solidus_auth_devise
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: solidus_core
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: solidus_support
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: capybara
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.17'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.17'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: factory_bot_rails
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '4.8'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '4.8'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: ffaker
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec-rails
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 3.7.2
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 3.7.2
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: shoulda-matchers
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.1'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.1'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: sqlite3
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: vcr
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: webmock
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: 3.4.1
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - "~>"
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: 3.4.1
|
195
|
+
description:
|
196
|
+
email: ngelx@protonmail.com
|
197
|
+
executables: []
|
198
|
+
extensions: []
|
199
|
+
extra_rdoc_files: []
|
200
|
+
files:
|
201
|
+
- LICENSE
|
202
|
+
- README.md
|
203
|
+
- app/controllers/spree/admin/product_imports_controller.rb
|
204
|
+
- app/jobs/import_products_job.rb
|
205
|
+
- app/models/solidus_import_products/parser.rb
|
206
|
+
- app/models/solidus_import_products/parser/base.rb
|
207
|
+
- app/models/solidus_import_products/parser/csv.rb
|
208
|
+
- app/models/spree/product_import.rb
|
209
|
+
- app/overrides/add_import_to_admin_sidebar_menu.rb
|
210
|
+
- app/services/solidus_import_products/create_variant.rb
|
211
|
+
- app/services/solidus_import_products/import.rb
|
212
|
+
- app/services/solidus_import_products/process_row.rb
|
213
|
+
- app/services/solidus_import_products/save_product.rb
|
214
|
+
- app/services/solidus_import_products/save_properties.rb
|
215
|
+
- app/services/solidus_import_products/update_product.rb
|
216
|
+
- app/views/spree/admin/product_imports/index.html.erb
|
217
|
+
- app/views/spree/admin/product_imports/show.html.erb
|
218
|
+
- app/views/spree/admin/shared/_import_sidebar_menu.erb
|
219
|
+
- app/views/spree/user_mailer/product_import_results.text.erb
|
220
|
+
- lib/generators/solidus_import_products/install/install_generator.rb
|
221
|
+
- lib/generators/solidus_import_products/install/templates/config/initializers/solidus_import_product_settings.rb
|
222
|
+
- lib/solidus_import_products.rb
|
223
|
+
- lib/solidus_import_products/engine.rb
|
224
|
+
- lib/solidus_import_products/exception.rb
|
225
|
+
- lib/solidus_import_products/import_helper.rb
|
226
|
+
- lib/solidus_import_products/logger.rb
|
227
|
+
- lib/solidus_import_products/user_mailer_ext.rb
|
228
|
+
- lib/tasks/solidus_import_products.rake
|
229
|
+
homepage: https://github.com/ngelx/solidus_import_products
|
230
|
+
licenses:
|
231
|
+
- MIT
|
232
|
+
metadata: {}
|
233
|
+
post_install_message:
|
234
|
+
rdoc_options: []
|
235
|
+
require_paths:
|
236
|
+
- lib
|
237
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
238
|
+
requirements:
|
239
|
+
- - ">="
|
240
|
+
- !ruby/object:Gem::Version
|
241
|
+
version: 2.2.2
|
242
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
243
|
+
requirements:
|
244
|
+
- - ">="
|
245
|
+
- !ruby/object:Gem::Version
|
246
|
+
version: '0'
|
247
|
+
requirements:
|
248
|
+
- none
|
249
|
+
rubyforge_project:
|
250
|
+
rubygems_version: 2.6.11
|
251
|
+
signing_key:
|
252
|
+
specification_version: 4
|
253
|
+
summary: solidus_import_products ... imports products. From a CSV file via Solidus's
|
254
|
+
Admin interface
|
255
|
+
test_files: []
|