Start CSV importer feature
This commit is contained in:
parent
5ec0e264ba
commit
939e2157ab
1
Gemfile
1
Gemfile
@ -44,6 +44,7 @@ gem "slim"
|
||||
gem "dartsass-rails"
|
||||
gem "bootstrap", "~> 5.3.3"
|
||||
gem "friendly_id", "~> 5.5.0"
|
||||
gem "csv"
|
||||
|
||||
group :development, :test do
|
||||
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
|
||||
|
@ -101,6 +101,7 @@ GEM
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.3)
|
||||
crass (1.0.6)
|
||||
csv (3.3.4)
|
||||
dartsass-rails (0.5.1)
|
||||
railties (>= 6.0.0)
|
||||
sass-embedded (~> 1.63)
|
||||
@ -432,6 +433,7 @@ DEPENDENCIES
|
||||
bootstrap (~> 5.3.3)
|
||||
brakeman
|
||||
capybara
|
||||
csv
|
||||
dartsass-rails
|
||||
debug
|
||||
factory_bot_rails
|
||||
|
@ -42,6 +42,21 @@ class ContestantsController < ApplicationController
|
||||
redirect_to contest_path(@contest)
|
||||
end
|
||||
|
||||
def import
|
||||
authorize @contest
|
||||
|
||||
if params[:csv_import]
|
||||
@csv_import = CsvImport.new(params.require(:csv_import).permit(:file, :separator))
|
||||
if @csv_import.save
|
||||
@csv_import = CsvImport.new
|
||||
else
|
||||
render :import, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
@csv_import = CsvImport.new
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_contest
|
||||
|
46
app/models/csv_import.rb
Normal file
46
app/models/csv_import.rb
Normal file
@ -0,0 +1,46 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: csv_imports
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# separator :integer not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
class CsvImport < ApplicationRecord
|
||||
enum :separator, { comma: ",", semicolon: ";" }, default: :comma
|
||||
|
||||
has_one_attached :file
|
||||
|
||||
validates :file, presence: true
|
||||
validate :acceptable_csv, on: :create
|
||||
|
||||
def acceptable_csv
|
||||
return unless file.attached?
|
||||
|
||||
if file.blob.byte_size > 20
|
||||
errors.add(:file, "this csv file is too large, it must be under 20MB")
|
||||
return
|
||||
end
|
||||
|
||||
if file.content_type != "text/csv"
|
||||
errors.add(:file, :not_a_csv_file)
|
||||
return
|
||||
end
|
||||
|
||||
begin
|
||||
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] == "")
|
||||
rescue CSV::MalformedCSVError => e
|
||||
errors.add(:file, e.message)
|
||||
end
|
||||
end
|
||||
|
||||
def options_for_separator
|
||||
keys = self.class.separators.keys
|
||||
keys.map(&:humanize).zip(keys).to_h
|
||||
end
|
||||
end
|
@ -31,6 +31,10 @@ class ContestPolicy < ApplicationPolicy
|
||||
record.user.id == user.id || user.admin?
|
||||
end
|
||||
|
||||
def import?
|
||||
record.user.id == user.id || user.admin?
|
||||
end
|
||||
|
||||
def scoreboard?
|
||||
true
|
||||
end
|
||||
|
19
app/views/contestants/import.html.slim
Normal file
19
app/views/contestants/import.html.slim
Normal file
@ -0,0 +1,19 @@
|
||||
= form_with(model: @csv_import, url: contest_import_path(@contest), html: { novalidate: true }) do |form|
|
||||
.row.g-3
|
||||
.col
|
||||
.mb-3
|
||||
.form-floating
|
||||
= form.file_field :file, class: "form-control", accept: ".csv, text/csv"
|
||||
= form.label :file
|
||||
|
||||
.row.g-3
|
||||
.col
|
||||
.mb-3
|
||||
.form-floating
|
||||
= form.select :separator, @csv_import.options_for_separator, {}, class: "form-select"
|
||||
= form.label :separator
|
||||
|
||||
.row.g-3
|
||||
.col
|
||||
.mb-3
|
||||
= form.submit t("helpers.buttons.import"), class: "btn btn-primary"
|
@ -76,6 +76,8 @@
|
||||
= t("contestants.plural").capitalize
|
||||
a.ms-3.btn.btn-primary href=new_contest_contestant_path(@contest) style="margin-top: -3px"
|
||||
| + #{t("helpers.buttons.add")}
|
||||
a.ms-3.btn.btn-primary href=contest_import_path(@contest) style="margin-top: -3px"
|
||||
| #{t("helpers.buttons.import")}
|
||||
table.table.table-striped.table-hover
|
||||
thead
|
||||
tr
|
||||
|
@ -49,6 +49,12 @@ en:
|
||||
invalid: "Allowed formats: xx:xx:xx, x:xx:xx, xx:xx, x:xx"
|
||||
puzzle_id:
|
||||
taken: "This contestant has already completed this puzzle"
|
||||
csv_import:
|
||||
attributes:
|
||||
file:
|
||||
blank: "No file selected"
|
||||
empty: "This file is empty"
|
||||
not_a_csv_file: "it must be a CSV file"
|
||||
completions:
|
||||
edit:
|
||||
title: "Edit completion"
|
||||
@ -75,6 +81,8 @@ en:
|
||||
edit:
|
||||
title: "Participant"
|
||||
team_title: "Teams"
|
||||
import:
|
||||
title: "Import participants from a CSV file"
|
||||
new:
|
||||
title: "New participant"
|
||||
team_title: "New team"
|
||||
@ -87,6 +95,7 @@ en:
|
||||
buttons:
|
||||
add: "Add"
|
||||
create: "Create"
|
||||
import: "CSV Import"
|
||||
save: "Save"
|
||||
messages:
|
||||
convert:
|
||||
|
@ -20,6 +20,12 @@ fr:
|
||||
invalid: "Formats autorisés: xx:xx:xx, x:xx:xx, xx:xx, x:xx"
|
||||
puzzle_id:
|
||||
taken: "Ce.tte participant.e a déjà complété ce puzzle"
|
||||
csv_import:
|
||||
attributes:
|
||||
file:
|
||||
blank: "Aucun fichier sélectionné"
|
||||
empty: "Ce fichier est vide"
|
||||
not_a_csv_file: "Le fichier doit être au format CSV"
|
||||
completions:
|
||||
edit:
|
||||
title: "Modifier la complétion"
|
||||
@ -46,6 +52,8 @@ fr:
|
||||
edit:
|
||||
title: "Participant.e"
|
||||
team_title: "Équipe"
|
||||
import:
|
||||
title: "Importer des participant.e.s"
|
||||
new:
|
||||
title: "Nouveau.elle participant.e"
|
||||
team_title: "Nouvelle équipe"
|
||||
@ -58,6 +66,7 @@ fr:
|
||||
buttons:
|
||||
add: "Ajouter"
|
||||
create: "Créer"
|
||||
import: "Importer un CSV"
|
||||
save: "Modifier"
|
||||
messages:
|
||||
convert:
|
||||
|
@ -15,6 +15,8 @@ Rails.application.routes.draw do
|
||||
resources :messages, only: :destroy do
|
||||
get "convert", to: "messages#convert"
|
||||
end
|
||||
get "import", to: "contestants#import"
|
||||
post "import", to: "contestants#import"
|
||||
end
|
||||
resources :passwords, param: :token
|
||||
resource :session
|
||||
|
9
db/migrate/20250517083830_create_csv_imports.rb
Normal file
9
db/migrate/20250517083830_create_csv_imports.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class CreateCsvImports < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :csv_imports do |t|
|
||||
t.string :separator, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
8
db/schema.rb
generated
8
db/schema.rb
generated
@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_05_15_062154) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_05_17_083830) do
|
||||
create_table "active_storage_attachments", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "record_type", null: false
|
||||
@ -75,6 +75,12 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_15_062154) do
|
||||
t.index ["user_id"], name: "index_contests_on_user_id"
|
||||
end
|
||||
|
||||
create_table "csv_imports", force: :cascade do |t|
|
||||
t.string "separator", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "friendly_id_slugs", force: :cascade do |t|
|
||||
t.string "slug", null: false
|
||||
t.integer "sluggable_id", null: false
|
||||
|
14
spec/factories/csv_imports.rb
Normal file
14
spec/factories/csv_imports.rb
Normal file
@ -0,0 +1,14 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: csv_imports
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# separator :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
FactoryBot.define do
|
||||
factory :csv_import do
|
||||
separator { 1 }
|
||||
end
|
||||
end
|
14
spec/models/csv_import_spec.rb
Normal file
14
spec/models/csv_import_spec.rb
Normal file
@ -0,0 +1,14 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: csv_imports
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# separator :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe CsvImport, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user