Implement CSV import and conversion to contestants
This commit is contained in:
parent
939e2157ab
commit
ec2201f9a8
@ -45,15 +45,48 @@ class ContestantsController < ApplicationController
|
|||||||
def import
|
def import
|
||||||
authorize @contest
|
authorize @contest
|
||||||
|
|
||||||
if params[:csv_import]
|
@csv_import = CsvImport.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def upload_csv
|
||||||
|
authorize @contest
|
||||||
|
|
||||||
@csv_import = CsvImport.new(params.require(:csv_import).permit(:file, :separator))
|
@csv_import = CsvImport.new(params.require(:csv_import).permit(:file, :separator))
|
||||||
if @csv_import.save
|
if @csv_import.save
|
||||||
@csv_import = CsvImport.new
|
redirect_to "/contests/#{@contest.id}/import/#{@csv_import.id}"
|
||||||
else
|
else
|
||||||
render :import, status: :unprocessable_entity
|
render :import, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def convert_csv
|
||||||
|
authorize @contest
|
||||||
|
|
||||||
|
@csv_import = CsvImport.find(params[:id])
|
||||||
|
@content = JSON.parse(@csv_import.content)
|
||||||
|
@form = Forms::CsvConversionForm.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def finalize_import
|
||||||
|
authorize @contest
|
||||||
|
|
||||||
|
@csv_import = CsvImport.find(params[:id])
|
||||||
|
@content = JSON.parse(@csv_import.content)
|
||||||
|
all_params = params.require(:forms_csv_conversion_form)
|
||||||
|
@form = Forms::CsvConversionForm.new(params.require(:forms_csv_conversion_form).permit(:email_column, :name_column))
|
||||||
|
if @form.valid?
|
||||||
|
@content.each_with_index do |row, i|
|
||||||
|
if all_params["row_#{i}".to_sym] == "1"
|
||||||
|
if @form.email_column == -1
|
||||||
|
Contestant.create(name: row[@form.name_column], contest: @contest)
|
||||||
else
|
else
|
||||||
@csv_import = CsvImport.new
|
Contestant.create(name: row[@form.name_column], email: row[@form.email_column], contest: @contest)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
redirect_to contest_path(@contest)
|
||||||
|
else
|
||||||
|
render :convert_csv, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
14
app/lib/forms.rb
Normal file
14
app/lib/forms.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module Forms
|
||||||
|
class CsvConversionForm
|
||||||
|
include ActiveModel::Model
|
||||||
|
include ActiveModel::Attributes
|
||||||
|
include ActiveModel::Validations::Callbacks
|
||||||
|
include ActiveRecord::Transactions
|
||||||
|
|
||||||
|
attribute :name_column, :integer
|
||||||
|
attribute :email_column, :integer
|
||||||
|
|
||||||
|
validates :name_column, presence: true
|
||||||
|
validates_numericality_of :name_column, greater_than: -1
|
||||||
|
end
|
||||||
|
end
|
@ -3,23 +3,26 @@
|
|||||||
# Table name: csv_imports
|
# Table name: csv_imports
|
||||||
#
|
#
|
||||||
# id :integer not null, primary key
|
# id :integer not null, primary key
|
||||||
# separator :integer not null
|
# content :string not null
|
||||||
|
# separator :string not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
#
|
#
|
||||||
class CsvImport < ApplicationRecord
|
class CsvImport < ApplicationRecord
|
||||||
enum :separator, { comma: ",", semicolon: ";" }, default: :comma
|
enum :separator, { comma: ",", semicolon: ";" }, suffix: true, default: :comma
|
||||||
|
|
||||||
has_one_attached :file
|
has_one_attached :file
|
||||||
|
|
||||||
validates :file, presence: true
|
validates :file, presence: true
|
||||||
validate :acceptable_csv, on: :create
|
validate :acceptable_csv, on: :create
|
||||||
|
|
||||||
|
before_save :read_csv
|
||||||
|
|
||||||
def acceptable_csv
|
def acceptable_csv
|
||||||
return unless file.attached?
|
return unless file.attached?
|
||||||
|
|
||||||
if file.blob.byte_size > 20
|
if file.blob.byte_size > 5 * 1024 * 1024
|
||||||
errors.add(:file, "this csv file is too large, it must be under 20MB")
|
errors.add(:file, "this csv file is too large, it must be under 5MB")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -31,14 +34,16 @@ class CsvImport < ApplicationRecord
|
|||||||
begin
|
begin
|
||||||
csv = CSV.read(attachment_changes["file"].attachable.path, col_sep: separator)
|
csv = CSV.read(attachment_changes["file"].attachable.path, col_sep: separator)
|
||||||
|
|
||||||
logger = Logger.new(STDOUT)
|
|
||||||
logger.info(csv)
|
|
||||||
errors.add(:file, :empty) if csv.count < 1 || (csv.count == 1 && csv[0].count == 1 && csv[0][0] == "")
|
errors.add(:file, :empty) if csv.count < 1 || (csv.count == 1 && csv[0].count == 1 && csv[0][0] == "")
|
||||||
rescue CSV::MalformedCSVError => e
|
rescue CSV::MalformedCSVError => e
|
||||||
errors.add(:file, e.message)
|
errors.add(:file, e.message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def read_csv
|
||||||
|
self.content = JSON.dump(CSV.read(attachment_changes["file"].attachable.path, col_sep: separator_for_database))
|
||||||
|
end
|
||||||
|
|
||||||
def options_for_separator
|
def options_for_separator
|
||||||
keys = self.class.separators.keys
|
keys = self.class.separators.keys
|
||||||
keys.map(&:humanize).zip(keys).to_h
|
keys.map(&:humanize).zip(keys).to_h
|
||||||
|
@ -19,10 +19,18 @@ class ContestPolicy < ApplicationPolicy
|
|||||||
record.user.id == user.id || user.admin?
|
record.user.id == user.id || user.admin?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def convert_csv?
|
||||||
|
record.user.id == user.id || user.admin?
|
||||||
|
end
|
||||||
|
|
||||||
def edit?
|
def edit?
|
||||||
record.user.id == user.id || user.admin?
|
record.user.id == user.id || user.admin?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def finalize_import?
|
||||||
|
record.user.id == user.id || user.admin?
|
||||||
|
end
|
||||||
|
|
||||||
def update?
|
def update?
|
||||||
record.user.id == user.id || user.admin?
|
record.user.id == user.id || user.admin?
|
||||||
end
|
end
|
||||||
@ -38,4 +46,8 @@ class ContestPolicy < ApplicationPolicy
|
|||||||
def scoreboard?
|
def scoreboard?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def upload_csv?
|
||||||
|
record.user.id == user.id || user.admin?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
40
app/views/contestants/convert_csv.html.slim
Normal file
40
app/views/contestants/convert_csv.html.slim
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
= form_with model: @form, url: "/contests/#{@contest.id}/import/#{@csv_import.id}" do |form|
|
||||||
|
|
||||||
|
.row.mb-3
|
||||||
|
.col
|
||||||
|
.form-floating
|
||||||
|
= form.select :name_column, [[t("helpers.none"), -1]] + Array.new(@content[0].count) {|i| [t("helpers.field") + "_#{i}", i] }, {}, class: "form-select"
|
||||||
|
= form.label :name_column
|
||||||
|
= t("contestants.import.name_column")
|
||||||
|
|
||||||
|
.row.mb-3
|
||||||
|
.col
|
||||||
|
.form-floating
|
||||||
|
= form.select :email_column, [[t("helpers.none"), -1]] + Array.new(@content[0].count) {|i| [t("helpers.field") + "_#{i}", i] }, {}, class: "form-select"
|
||||||
|
= form.label :email_column
|
||||||
|
= t("contestants.import.email_column")
|
||||||
|
|
||||||
|
.row.g-3
|
||||||
|
.col
|
||||||
|
table.table.table-striped.table-hover
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
- @content[0].each_with_index do |_, i|
|
||||||
|
th scope="col"
|
||||||
|
= t("helpers.field") + "_#{i}"
|
||||||
|
th scope="col"
|
||||||
|
= t("contestants.import.import_column")
|
||||||
|
tbody
|
||||||
|
- @content.each_with_index do |row, i|
|
||||||
|
tr scope="row"
|
||||||
|
- row.each do |value|
|
||||||
|
td
|
||||||
|
= value
|
||||||
|
td
|
||||||
|
.form-check.form-switch
|
||||||
|
= form.check_box "row_#{i}".to_sym, class: "form-check-input", checked: true
|
||||||
|
|
||||||
|
.row.g-3
|
||||||
|
.col
|
||||||
|
= form.submit t("helpers.buttons.confirm"), class: "btn btn-primary"
|
||||||
|
|
@ -28,6 +28,14 @@
|
|||||||
# enabled: "ON"
|
# enabled: "ON"
|
||||||
|
|
||||||
en:
|
en:
|
||||||
|
activemodel:
|
||||||
|
errors:
|
||||||
|
models:
|
||||||
|
forms/csv_conversion_form:
|
||||||
|
attributes:
|
||||||
|
name_column:
|
||||||
|
blank: "Participant names are required"
|
||||||
|
greater_than: "Participant names are required"
|
||||||
activerecord:
|
activerecord:
|
||||||
attributes:
|
attributes:
|
||||||
contest:
|
contest:
|
||||||
@ -78,11 +86,18 @@ en:
|
|||||||
add_puzzle: "Add puzzle"
|
add_puzzle: "Add puzzle"
|
||||||
public_scoreboard: "Public scoreboard: "
|
public_scoreboard: "Public scoreboard: "
|
||||||
contestants:
|
contestants:
|
||||||
|
convert_csv:
|
||||||
|
title: "Import participants"
|
||||||
edit:
|
edit:
|
||||||
title: "Participant"
|
title: "Participant"
|
||||||
team_title: "Teams"
|
team_title: "Teams"
|
||||||
|
finalize_import:
|
||||||
|
title: "Import participants"
|
||||||
import:
|
import:
|
||||||
title: "Import participants from a CSV file"
|
email_column: "Participant email"
|
||||||
|
import_column: "Import?"
|
||||||
|
name_column: "Participant name"
|
||||||
|
title: "Import participants"
|
||||||
new:
|
new:
|
||||||
title: "New participant"
|
title: "New participant"
|
||||||
team_title: "New team"
|
team_title: "New team"
|
||||||
@ -94,9 +109,11 @@ en:
|
|||||||
helpers:
|
helpers:
|
||||||
buttons:
|
buttons:
|
||||||
add: "Add"
|
add: "Add"
|
||||||
|
confirm: "Confirm"
|
||||||
create: "Create"
|
create: "Create"
|
||||||
import: "CSV Import"
|
import: "CSV Import"
|
||||||
save: "Save"
|
save: "Save"
|
||||||
|
field: "Field"
|
||||||
messages:
|
messages:
|
||||||
convert:
|
convert:
|
||||||
title: "Convert message into completion"
|
title: "Convert message into completion"
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
fr:
|
fr:
|
||||||
|
activemodel:
|
||||||
|
errors:
|
||||||
|
models:
|
||||||
|
forms/csv_conversion_form:
|
||||||
|
attributes:
|
||||||
|
name_column:
|
||||||
|
blank: "Choisir une colonne pour les noms des participant.e.s est nécessaire"
|
||||||
|
greater_than: "Choisir une colonne pour les noms des participant.e.s est nécessaire"
|
||||||
activerecord:
|
activerecord:
|
||||||
attributes:
|
attributes:
|
||||||
contest:
|
contest:
|
||||||
@ -49,10 +57,17 @@ fr:
|
|||||||
add_puzzle: "Ajouter un puzzle"
|
add_puzzle: "Ajouter un puzzle"
|
||||||
public_scoreboard: "Classement public : "
|
public_scoreboard: "Classement public : "
|
||||||
contestants:
|
contestants:
|
||||||
|
convert_csv:
|
||||||
|
title: "Importer des participant.e.s"
|
||||||
edit:
|
edit:
|
||||||
title: "Participant.e"
|
title: "Participant.e"
|
||||||
team_title: "Équipe"
|
team_title: "Équipe"
|
||||||
|
finalize_import:
|
||||||
|
title: "Importer des participant.e.s"
|
||||||
import:
|
import:
|
||||||
|
email_column: "Email des participant.e.s"
|
||||||
|
import_column: "Importer ?"
|
||||||
|
name_column: "Noms des participant.e.s"
|
||||||
title: "Importer des participant.e.s"
|
title: "Importer des participant.e.s"
|
||||||
new:
|
new:
|
||||||
title: "Nouveau.elle participant.e"
|
title: "Nouveau.elle participant.e"
|
||||||
@ -65,9 +80,11 @@ fr:
|
|||||||
helpers:
|
helpers:
|
||||||
buttons:
|
buttons:
|
||||||
add: "Ajouter"
|
add: "Ajouter"
|
||||||
|
confirm: "Confirmer"
|
||||||
create: "Créer"
|
create: "Créer"
|
||||||
import: "Importer un CSV"
|
import: "Importer un CSV"
|
||||||
save: "Modifier"
|
save: "Modifier"
|
||||||
|
field: "Champ"
|
||||||
messages:
|
messages:
|
||||||
convert:
|
convert:
|
||||||
title: "Conversion d'un message en complétion"
|
title: "Conversion d'un message en complétion"
|
||||||
|
@ -16,7 +16,9 @@ Rails.application.routes.draw do
|
|||||||
get "convert", to: "messages#convert"
|
get "convert", to: "messages#convert"
|
||||||
end
|
end
|
||||||
get "import", to: "contestants#import"
|
get "import", to: "contestants#import"
|
||||||
post "import", to: "contestants#import"
|
post "import", to: "contestants#upload_csv"
|
||||||
|
get "import/:id", to: "contestants#convert_csv"
|
||||||
|
post "import/:id", to: "contestants#finalize_import"
|
||||||
end
|
end
|
||||||
resources :passwords, param: :token
|
resources :passwords, param: :token
|
||||||
resource :session
|
resource :session
|
||||||
|
5
db/migrate/20250517131707_add_content_to_csv_import.rb
Normal file
5
db/migrate/20250517131707_add_content_to_csv_import.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class AddContentToCsvImport < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
add_column :csv_imports, :content, :string, null: false
|
||||||
|
end
|
||||||
|
end
|
3
db/schema.rb
generated
3
db/schema.rb
generated
@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2025_05_17_083830) do
|
ActiveRecord::Schema[8.0].define(version: 2025_05_17_131707) do
|
||||||
create_table "active_storage_attachments", force: :cascade do |t|
|
create_table "active_storage_attachments", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "record_type", null: false
|
t.string "record_type", null: false
|
||||||
@ -79,6 +79,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_17_083830) do
|
|||||||
t.string "separator", null: false
|
t.string "separator", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.string "content", null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "friendly_id_slugs", force: :cascade do |t|
|
create_table "friendly_id_slugs", force: :cascade do |t|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
# Table name: csv_imports
|
# Table name: csv_imports
|
||||||
#
|
#
|
||||||
# id :integer not null, primary key
|
# id :integer not null, primary key
|
||||||
|
# content :string not null
|
||||||
# separator :string not null
|
# separator :string not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
# Table name: csv_imports
|
# Table name: csv_imports
|
||||||
#
|
#
|
||||||
# id :integer not null, primary key
|
# id :integer not null, primary key
|
||||||
|
# content :string not null
|
||||||
# separator :string not null
|
# separator :string not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
|
Loading…
x
Reference in New Issue
Block a user